Skip to content

API Signature Verification

When our system sends a callback request to your endpoint, the request will include a signature header so you can verify the request is authentic and has not been tampered with.

3.1 Integration Information

The system provides the following information to the integrated partner:

PropertyTypeDescription
signatureKeystringA secret key (UUID format) used for HMAC-SHA256 signature generation from Back Office.

3.2 Request Headers

Each callback request includes the following headers. Use both values to verify the signature on your server:

HeaderTypeDescription
sapi-timestampstringUnix timestamp in milliseconds sent by the system at request time.
sapi-signaturestringHMAC-SHA256 hex digest of the signing string, using signatureKey as the secret.

3.3 Signature Generation

The signature is generated using HMAC-SHA256.

Signing String

Build the signing string by concatenating the sapi-timestamp header value and the raw JSON body string:

signingString = rawBodyString + "." + sapi-timestamp
PartTypeDescription
rawBodyStringstringThe raw JSON-serialized request body (before any parsing).
sapi-timestampstringValue taken directly from the sapi-timestamp request header.

HMAC-SHA256

signature = HMAC-SHA256(secret=signatureKey, data=signingString).hex()

3.4 Examples

Raw Data

PropertyValue
signatureKeyxxxxxxxxx-xxxx-xxxx-xxxx-xxxxx
sapi-timestamp1776929280534
Request Body (JSON){"id":"1db0f513-a31f-4afa-9def-fdd6d2398c22","currency":"THB","productId":"5G_GAMES","timestampMillis":1776929280534,"username":"testaoo0012"}

Step 1 — Serialize the request body

rawBodyString = '{"id":"1db0f513-a31f-4afa-9def-fdd6d2398c22","currency":"THB","productId":"5G_GAMES","timestampMillis":1776929280534,"username":"testaoo0012"}'

Step 2 — Build the signing string

signingString = rawBodyString + "." + "1776929280534"
             = "{"id":"1db0f513-a31f-4afa-9def-fdd6d2398c22","currency":"THB","productId":"5G_GAMES","timestampMillis":1776929280534,"username":"testaoo0012"}.1776929280534"

Step 3 — Compute HMAC-SHA256

signature = HMAC-SHA256(
  secret = "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxx",
  data   = signingString
).hex()

3.5 Signature Example Code

JavaScript / Node.js

javascript
const crypto = require("crypto");

const buildSigningString = (timestamp, rawBody) => {
  return `${rawBody}.${timestamp}`;
};

const generate = (secret, timestamp, rawBody) => {
  const data = buildSigningString(timestamp, rawBody);
  return crypto.createHmac("sha256", secret).update(data).digest("hex");
};

// --- Example usage ---
const signatureKey = "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxx";
const timestamp = "1776929280534";

const requestBody = {
  id: "1db0f513-a31f-4afa-9def-fdd6d2398c22",
  currency: "THB",
  productId: "5G_GAMES",
  timestampMillis: 1776929280534,
  username: "testaoo0012",
};

const rawBody = JSON.stringify(requestBody);
const signature = generate(signatureKey, timestamp, rawBody);
return signature;

PHP

php
<?php

function buildSigningString(string $timestamp, string $rawBody): string {
    return $rawBody . '.' . $timestamp;
}

function generateSignature(string $signatureKey, string $timestamp, string $rawBody): string {
    $signingString = buildSigningString($timestamp, $rawBody);
    return hash_hmac('sha256', $signingString, $signatureKey);
}

// --- Example usage ---
$signatureKey   = 'xxxxxxxxx-xxxx-xxxx-xxxx-xxxxx';
$timestamp = '1776929280534';

$requestBody = [
    'id'              => '1db0f513-a31f-4afa-9def-fdd6d2398c22',
    'currency'        => 'THB',
    'productId'       => '5G_GAMES',
    'timestampMillis' => 1776929280534,
    'username'        => 'testaoo0012',
];

// Verification: read timestamp from header
$timestamp = $_SERVER['HTTP_SAPI_TIMESTAMP'];
$received  = $_SERVER['HTTP_SAPI_SIGNATURE'];
$rawBody   = file_get_contents('php://input');

$expected  = generateSignature($signatureKey, $timestamp, $rawBody);
$valid     = hash_equals($expected, $received);

if (!$valid) {
    http_response_code(401);
    echo json_encode(['message' => 'Invalid signature']);
    exit;
}

3.6 Verification Flow

When receiving a callback request, follow these steps:

  1. Read sapi-timestamp and sapi-signature from the request headers.
  2. Get the raw (unparsed) request body string.
  3. Build the signing string: rawBody + "." + sapi-timestamp.
  4. Re-compute HMAC-SHA256 using your signatureKey.
  5. Compare with sapi-signature using constant-time comparison to prevent timing attacks.
  6. If the signatures do not match, respond with statusCode: 30002 (Invalid Signature).
Headers received:
  sapi-timestamp: 1776929280534
  sapi-signature: <hex string>

Verification:
  signingString = "1776929280534." + rawBody
  expected      = HMAC-SHA256(signatureKey, signingString).hex()
  valid         = timingSafeEqual(expected, sapi-signature)

If not valid → respond with statusCode 30002

See Common Status Code for the full list of status codes.