Signing Requests
Every cashout call — balances, create, status — must be signed with the secret you generated when you enabled the API. All requests also carry your X-Api-Key and must come from an allowlisted IP.
The two signing headers
| Header | Type | Required | Description | Example |
|---|---|---|---|---|
X-Timestamp | string | Yes | Current Unix time in seconds. Rejected (signature_stale) if more than 5 minutes from the server clock. | 1783369120 |
X-Signature | string (hex) | Yes | HMAC_SHA256(secret, X-Timestamp + "." + rawBody), hex-encoded. For GET requests the body is the empty string. | a7b9… |
The signing string is the timestamp, a literal ., then the exact raw request body you send. Compute the signature over the bytes you actually transmit — any change to the body (re-serialization, key reordering, whitespace) invalidates it.
Sign the exact raw bytes
Build the signature from the same byte string you put on the wire — not a re-encoded copy. Serialize the body once, sign that string, and send that string.
Node.js
import crypto from 'node:crypto';
function sign(secret, body) {
const ts = Math.floor(Date.now() / 1000).toString();
const sig = crypto.createHmac('sha256', secret).update(`${ts}.${body}`).digest('hex');
return { ts, sig };
}
Shell (openssl)
TS=$(date +%s); BODY="" # empty for GET; the exact JSON string for POST
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | awk '{print $2}')
Next
- Get Balances — your first signed call.
- Cashout Errors —
signature_*failures and how to fix them.