Authentication and safe retries
Exact HMAC signing, replay protection and financial intent handling.
Sign exact bytes
All integration routes in the catalogue require these headers:
X-Crisp-Partner-Key: cpk_…
X-Crisp-Timestamp: ISO-8601 UTC timestamp
X-Crisp-Nonce: unique nonce, 16–128 characters
X-Crisp-Signature: base64 HMAC-SHA-256Canonical input (newline separators, no final newline):
timestamp
nonce
UPPERCASE_METHOD
/v1/exact-percent-encoded-path
sha256_hex(raw_body_bytes)The path includes /v1 and excludes the query string under the current
contract. Sign the encoded path actually sent; do not decode and rebuild it.
An empty body has the SHA-256 of zero bytes. Serialize JSON once, sign those
bytes, and send the same bytes. HTTPS is mandatory; signatures do not replace TLS.
The downloadable client implements this format.
Nonce characters: letters, digits, ., _, ~, -; length 16–128. Default
timestamp tolerance is five minutes. Nonces are single-use, including GETs.
Traffic ingestion is the designated exception: durable tenant/event ID deduplication
is used instead of persisting another nonce row. Always generate a fresh nonce.
Idempotency is not authentication
Financial and other durable mutations marked Required in the catalogue need
Idempotency-Key: 8–200 letters/digits or ., _, :, -.
- Persist one intent key before the first submit. Use it for retries of that same path, user and byte-identical body; never recycle it for another intent.
- Generate new authentication timestamp/nonce/signature for every attempt.
- A double-click, timeout or 502 does not mean the original action failed.
- Do not automatically generate a new key to get around an in-progress or ambiguous response. Correct the cause or reconcile the existing action.
- Quotes, prewarm, realtime sessions and capability minting are not durable
money movements and do not require that header. Page views use
eventId.
Idempotency responses have retention; do not assume a key is permanent history. Keep your own durable action records. Contact CRISP before retrying an old uncertain financial request whose lifecycle cannot be established.
Responses
{ "data": { "status": "ready" }, "requestId": "request-uuid" }{ "error": { "code": "account_not_ready", "message": "…", "requestId": "request-uuid" } }| Status | Handling |
|---|---|
| 200 | Successful read/action; inspect resource state |
| 202 | Durable acceptance; NOT proof of execution, settlement or final transfer |
| 400 | Correct invalid fields; do not blindly retry |
| 401 | Check key, canonical bytes, clock and nonce |
| 403 | Scope/authority denied; do not retry with another user's identity |
| 404 | Check account/resource and authenticated tenant |
| 409 | Inspect code: in-progress, not-ready, reused nonce, conflict or ambiguous outcome are different cases |
| 429 | Back off, honor Retry-After when supplied; agree deployment limits with CRISP |
| 502/503 or timeout | Back off reads; preserve original financial intent and reconcile before replacement |
The example client surfaces errors/statuses and never auto-retries. Log safe request IDs, your intent ID and timestamps, not credentials or entire user payloads.