Skip to content

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

StepOperationOutput
1. request body → JCSSerialize the body in RFC 8785 sorted orderKeyA
2. JCS → hash(JCS)SHA-256(KeyA) → lowercase hexadecimal stringKeyB (bodyHash)
3. Build the canonical requestConcatenate version / bodyHash / timestamp / nonce / idempotencyKey directly, in ordercanonical request
4. Compute the signatureHMAC-SHA256(api_secret, canonical request) → Base64 encodedsignature
5. Add HTTP Headers and sendWrite 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 request

1. 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:

json
{
  "external_player_id": "player1",
  "player_name": "player1_name",
  "currency_code": "CNY"
}

After JCS canonicalization (keys sorted lexicographically, no extra whitespace):

json
{"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:

text
KeyA = "{}"
KeyB = sha256("{}") =
44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a

TIP

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:

FieldValueDescription
<version>v1Fixed value
<bodyHash>KeyBThe 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:

text
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.

text
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:

HeaderDescription
X-API-Keyapi_key
X-SignatureThe signature from step 4
X-TimestampThe <timestamp> from step 3
X-NonceThe <nonce> from step 3
X-Body-HashKeyB (required even for GET; value is sha256("{}"))
X-Signature-Versionv1
X-Idempotency-KeyThe <idempotencyKey> from step 3
Content-Typeapplication/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

json
{
  "currencyCode": "USD",
  "externalPlayerId": "player123",
  "playerName": "player123"
}

Step 1 — JCS Serialization (KeyA)

Keys sorted lexicographically, no extra whitespace:

text
{"currencyCode":"USD","externalPlayerId":"player123","playerName":"player123"}

Step 2 — Body Hash (KeyB)

SHA-256(KeyA) → lowercase hexadecimal:

text
f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e322

Step 3 — Canonical Request

v1 + KeyB + timestamp + nonce + idempotencyKey, concatenated directly:

text
v1f6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e32217817550475420111f43e-ae1e-4608-a406-cb7bffe74fbed037c7a1-4238-425c-86e5-928fac930806

Step 4 — Signature

HMAC-SHA256(api_secret, canonical request) → Base64:

text
VoVe8PcLPuuneFU/kAkLGGgpqFNIH6sA+VnRKnUFeoA=

Complete Headers

HeaderValue
Content-Typeapplication/json
X-API-KeyI0fRgq1DV79L8b37m9LdHjtJQgNrcC1udH-LwXNxLhW2b2vXZzCPkGoLVDP3mww2
X-Timestamp1781755047542
X-Nonce0111f43e-ae1e-4608-a406-cb7bffe74fbe
X-Body-Hashf6f32180d3036e5e4d7584cb11ec828986fcdfb742a502aeb1af2f9ebb07e322
X-Signature-Versionv1
X-Idempotency-Keyd037c7a1-4238-425c-86e5-928fac930806
X-SignatureVoVe8PcLPuuneFU/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.

BFX EXCHANGE · BE THE GAME CHANGER