Preview documentation. The interface is being rolled out — examples are indicative, and we will confirm final values at launch.
Authentication: HMAC, key ID, scopes
Every request to Commerce API v1 is signed with an HMAC key. The key consists of
a public key_id and a secret. We show the secret only once — when the key is
created — so save it immediately in a secure place. In examples, the key ID is
always masked: cba_test_••••••••.
Key model
- key_id — public identifier, sent in the
X-CBA-Key-Idheader. - secret — secret material for computing the signature; never leaves your server.
- scopes — permission scopes in a deny-by-default model (without a granted
scope, the request is rejected with
403). - expiration — the key has an expiry date.
- rotation — key replacement with an overlap window for old and new keys.
Signature headers
Every request carries a complete set of headers. For mutating methods (POST,
PATCH, DELETE), also include Idempotency-Key.
| Header | Meaning |
|---|---|
X-CBA-Key-Id |
Public key identifier |
X-CBA-Timestamp |
RFC 3339 UTC timestamp when the request was generated |
X-CBA-Nonce |
Unique one-time value per key within the replay window |
X-CBA-Content-SHA256 |
SHA-256 (hex) of the raw body |
X-CBA-Signature |
HMAC-SHA256 signature in hmac-sha256=… format |
Idempotency-Key |
Idempotency key (mutations only) |
How to compute the signature
The signature covers a canonical string built from six elements, joined by newlines in a fixed order:
- HTTP method (uppercase, e.g.
POST), - canonical path (e.g.
/api/v1/commerce/orders), - canonical query (sorted parameters, empty when none),
- timestamp (
X-CBA-Timestamp), - nonce (
X-CBA-Nonce), - body hash (
X-CBA-Content-SHA256).
Compute HMAC-SHA256 on that string using the secret and place the result in
X-CBA-Signature.
CANONICAL="POST\n/api/v1/commerce/orders\n\n2026-07-11T10:00:00Z\n$NONCE\n$BODY_SHA256" SIGNATURE=$(printf "%b" "$CANONICAL" | openssl dgst -sha256 -hmac "$SECRET" -binary | xxd -p -c 256) # header: X-CBA-Signature: hmac-sha256=$SIGNATURE
const canonical = [method, path, query, timestamp, nonce, bodySha256].join('\n');
const signature = crypto.createHmac('sha256', secret).update(canonical).digest('hex');
// header: `X-CBA-Signature: hmac-sha256=${signature}`$canonical = implode("\n", [$method, $path, $query, $timestamp, $nonce, $bodySha256]);
$signature = hash_hmac('sha256', $canonical, $secret);
// header: "X-CBA-Signature: hmac-sha256={$signature}"Replay window and nonce
The timestamp must fall within a 300-second window relative to server time —
keep clocks synchronized (NTP). The nonce must be unique per key in that
window; a repeated nonce is rejected as a replay attempt. Typical rejections are
described in Errors (401 invalid_signature,
expired_timestamp, replay_nonce).
Scopes
Permissions use deny-by-default — a key can only do what you explicitly grant. Assign the narrowest set you need.
| Scope | Access |
|---|---|
commerce:read |
Aggregate commerce data read |
catalog:read / catalog:write |
Catalog and variant read / write |
customers:read / customers:write |
Customer data read / write |
customers:erase |
Customer data erasure (right to be forgotten) |
orders:read / orders:write |
Order and intent read / create |
results:read |
Conversation results read |
rules:read / rules:write |
Sales rules read / write |
webhooks:read / webhooks:write |
Webhook management |
configuration:read |
Integration configuration read |
Rotation and reveal-once
The secret is shown once. When rotating, create a new key, deploy it in parallel (the overlap window lets old and new keys work at the same time), and after traffic confirms the new key, revoke the old one. This avoids an integration outage during key exchange.
Best practices
- Keep the secret server-side only; never in a client app or URL.
- Sign requests immediately before sending so you do not exceed the 300 s window.
- Log
correlation_idfrom error responses, but never log the secret. - More in Security and data.