2.2 HMAC Signing Flow
All API requests must include a cryptographic signature in the header. Follow the 5 steps below. A complete signing example is provided at the end so you can verify that your implementation is correct.
Step Overview
| Step | Operation | Output |
|---|---|---|
| 1. request body → JCS | Serialize the body in RFC 8785 sorted order | KeyA |
| 2. JCS → hash(JCS) | SHA-256(KeyA) → lowercase hexadecimal string | KeyB (bodyHash) |
| 3. Build the canonical request | Concatenate version / bodyHash / timestamp / nonce / idempotencyKey directly, in order | canonical request |
| 4. Compute the signature | HMAC-SHA256(api_secret, canonical request) → Base64 encoded | signature |
| 5. Add HTTP Headers and send | Write the signature and related fields into the HTTP headers, then send | — |
request body ──▶ JCS canonicalization (RFC 8785) ──▶ KeyA
KeyA ──▶ sha256(hex, lowercase) ──▶ KeyB (bodyHash)
KeyB ──▶ canonical request (5 fields, direct concatenation)
canonical request ──▶ HMAC-SHA256(api_secret) ──▶ Base64 ──▶ signature
signature + headers ──▶ send request1. request body → JCS
Convert the request body to JSON and sort it using JCS canonicalization (RFC 8785) to obtain KeyA.
Using login as an example, the request body is:
{
"external_player_id": "player1",
"player_name": "player1_name",
"currency_code": "CNY"
}After JCS canonicalization (keys sorted lexicographically, no extra whitespace):
{"currency_code":"CNY","external_player_id":"player1","player_name":"player1_name"}TIP
When a request has no body (for example, all GET APIs), you must use "{}" as KeyA — do not use an empty string "". Otherwise you will get code:10000 "authentication failed".
2. JCS → hash(JCS)
Compute the SHA-256 hash of KeyA and convert it to a lowercase hexadecimal string (hex) to obtain KeyB.
For example, the fixed value for an empty body:
KeyA = "{}"
KeyB = sha256("{}") =
44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8aTIP
An extra space, newline, or different key order will produce a completely different result.
3. Build the canonical request
Use the following fields as the input to HMAC:
| Field | Value | Description |
|---|---|---|
| <version> | v1 | Fixed value |
| <bodyHash> | KeyB | The hex computed in step 2 |
| <timestamp> | Timestamp (milliseconds), for example 1718105400000; the server allows a deviation of ±60 seconds from the current time, beyond which it returns an authentication failure | |
| <nonce> | Must be different for each request; must be a UUID | |
| <idempotencyKey> | Idempotency key, ensuring that resending the same operation does not take effect twice; must be a UUID |
Final canonical request format:
v1<KeyB><timestamp><nonce><idempotencyKey>TIP
Concatenate each field directly in the order above — do not add spaces, omit fields, or insert newlines, or the signature will differ.
4. Compute the signature
Compute HMAC-SHA256 over the canonical request using api_secret, then Base64 encode it to obtain the signature.
signature = base64( HMAC_SHA256(api_secret, canonical_request) )5. Add HTTP Headers and send the request
All APIs (GET and POST) must include the following headers:
| Header | Description |
|---|---|
| X-API-Key | api_key |
| X-Signature | The signature from step 4 |
| X-Timestamp | The <timestamp> from step 3 |
| X-Nonce | The <nonce> from step 3 |
| X-Body-Hash | KeyB (required even for GET; value is sha256("{}")) |
| X-Signature-Version | v1 |
| X-Idempotency-Key | The <idempotencyKey> from step 3 |
| Content-Type | application/json (required for POST only) |
Complete Signing Example (for self-testing)
The following is a set of real, verifiable data. Merchants can compare step by step to confirm that their implementation is correct.
Original Request Body
{
"currencyCode": "USD",
"externalPlayerId": "player123",
"playerName": "player123"
}Step 1 — JCS Serialization (KeyA)
Keys sorted lexicographically, no extra whitespace:
{"currencyCode":"USD","externalPlayerId":"player123","playerName":"player123"}Step 2 — Body Hash (KeyB)
SHA-256(KeyA) → lowercase hexadecimal:
f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e322Step 3 — Canonical Request
v1 + KeyB + timestamp + nonce + idempotencyKey, concatenated directly:
v1f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e32217817550475420111f43e-ae1e-4608-a406-cb7bffe74fbed037c7a1-4238-425c-86e5-928fac930806Step 4 — Signature
HMAC-SHA256(api_secret, canonical request) → Base64:
VoVe8PcLPuuneFU/kAkLGGgpqFNIH6sA+VnRKnUFeoA=Complete Headers
| Header | Value |
|---|---|
| Content-Type | application/json |
| X-API-Key | I0fRgq1DV79L8b37m9LdHjtJQgNrcC1udH-LwXNxLhW2b2vXZzCPkGoLVDP3mww2 |
| X-Timestamp | 1781755047542 |
| X-Nonce | 0111f43e-ae1e-4608-a406-cb7bffe74fbe |
| X-Body-Hash | f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e322 |
| X-Signature-Version | v1 |
| X-Idempotency-Key | d037c7a1-4238-425c-86e5-928fac930806 |
| X-Signature | VoVe8PcLPuuneFU/kAkLGGgpqFNIH6sA+VnRKnUFeoA= |
TIP
The api_secret used for signing is fx5PaVEVJkgwEwT_jAtTrj1PJCDvaq6XqshiUcJSo78. If your canonical request or signature does not match the above, check the output value of each step one by one.