Authentication
The Merchant API authenticates every request with an API key pair and an HMAC-SHA256 signature. There are no bearer tokens to refresh and no cookies — each request carries its own signature, computed from the request itself, so it cannot be replayed against a different route or after it has expired.
API keys
Create an API key pair in the merchant portal. A pair has two parts:
- Name
key id- Type
- string
- Description
The public identifier, prefixed
pk_live_orpk_test_. Sent in theX-Api-Keyheader. Safe to log.
- Name
secret- Type
- string
- Description
The shared HMAC secret, prefixed
sk_. Used to compute the signature and never sent over the wire. Shown only once at creation — store it securely.
A pk_test_ key runs in test mode (sandbox): charges run the full flow but never settle to
your live wallet. Use a test key while integrating, then switch to a pk_live_ key for
production. See Test mode.
The signing scheme
For each request, build a canonical string from five components joined by newlines (\n):
METHOD\nPATH\nCANONICAL_QUERY\nTIMESTAMP\nSHA256_HEX(BODY)
METHOD— the uppercase HTTP method, e.g.POST.PATH— the request path without the query string, e.g./api/v1/charges.CANONICAL_QUERY— the query string'skey=valuepairs sorted lexicographically and rejoined with&(an empty string when the request has no query). Sorting makes the signature independent of the order you send the parameters in.TIMESTAMP— the current Unix time in seconds, as a string. It must be within 5 minutes of the server clock or the request is rejected.SHA256_HEX(BODY)— the lowercase hex SHA-256 of the raw request body bytes. For a request with no body (mostGETs), this is the SHA-256 of the empty string.
The signature is the lowercase hex HMAC-SHA256(secret, canonical). Send these three headers with every request:
- Name
X-Api-Key- Type
- string
- Description
Your public key id, e.g.
pk_live_8f3aK2x9.
- Name
X-Timestamp- Type
- string
- Description
The same Unix-seconds timestamp used in the canonical string.
- Name
X-Signature- Type
- string
- Description
The lowercase hex HMAC-SHA256 signature.
The query string is part of the signature. When you call a list endpoint like
/api/v1/orders?status=paid&limit=10, sign the path /api/v1/orders and the canonical query
limit=10&status=paid (pairs sorted, rejoined with &). You may send the parameters on the URL
in any order — only the sorted form is signed — but every parameter you send must be included, or
the signature will not match.
Signing a request
The examples below sign and send a POST /api/v1/charges. The same helper works for any endpoint: pass the method, path, and (for write requests) the exact JSON body you will send.
Because the body is hashed, you must sign the exact bytes you transmit — serialize the JSON once and reuse that string for both the hash and the request body.
Signed request
#!/usr/bin/env bash
KEY_ID="pk_live_8f3aK2x9"
SECRET="sk_live_your_secret"
BASE="https://api.fluxa.example"
METHOD="POST"
PATH="/api/v1/charges"
QUERY="" # raw query for list endpoints, e.g. "limit=10&status=paid"
BODY='{"merchant_order_id":"order-1001","amount":"49.90","currency":"USDT","network":"tron"}'
TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')
# CANONICAL_QUERY: split on &, sort, rejoin (empty stays empty).
CANON_QUERY=$(printf '%s' "$QUERY" | tr '&' '\n' | sort | paste -sd '&' -)
CANON=$(printf '%s\n%s\n%s\n%s\n%s' "$METHOD" "$PATH" "$CANON_QUERY" "$TS" "$BODY_HASH")
SIG=$(printf '%s' "$CANON" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl "$BASE$PATH${QUERY:+?$QUERY}" \
-H "X-Api-Key: $KEY_ID" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-H "Content-Type: application/json" \
-d "$BODY"
Authentication errors
A request that fails authentication returns 401 Unauthorized with one of these codes in the error envelope:
- Name
missing_auth- Type
- 401
- Description
One or more of
X-Api-Key,X-Timestamp,X-Signatureis absent.
- Name
bad_timestamp- Type
- 401
- Description
X-Timestampis not a valid Unix-seconds integer.
- Name
stale_request- Type
- 401
- Description
The timestamp is more than 5 minutes from the server clock.
- Name
invalid_key- Type
- 401
- Description
The key id is unknown or has been revoked.
- Name
bad_signature- Type
- 401
- Description
The signature does not match — usually a body that differs from the bytes you hashed, the wrong path, or the wrong secret.
Test mode
A pk_test_ key authenticates exactly like a live key, but the orders it creates
are flagged is_test: true and never settle to your live wallet. Test orders run
the complete payment flow — instructions, confirmations, webhooks — so you can
build and verify your integration end to end before going live. Switch to a
pk_live_ key when you are ready for real money to move.