/api/v1/* is authenticated against a tenant’s signing_secret. Two modes are supported: HMAC signatures (recommended) and bearer tokens (simpler, equivalent strength when transport is TLS).
Required headers
If both
x-chert-signature and authorization are present, the signature is checked first.
Signature format
What gets signed
The HMAC input is the literal string<unix_seconds> + . + <raw_body>.
- For
POSTrequests,raw_bodyis the exact bytes you transmit. Do not re-serialize, do not strip whitespace, do not reorder keys after signing. - For
GETrequests,raw_bodyis the empty string. The HMAC input ends in a trailing dot.
Replay window
Timestamps more than 5 minutes off in either direction are rejected with401 and the distinct code 2013 AUTH_TIMESTAMP_SKEW — separate from a genuinely bad signature (2004). Sync your clock and resign on retry — never reuse a signature.
Reference implementations
Bearer token fallback
For scripts, prototypes, and trusted server-to-server calls, send the secret directly:Errors
Auth and signature failures return distinct numeric codes — they are no longer collapsed into one opaque “unauthorized”. Branch on the numericcode:
Error responses carry the standard envelope —
{ success, error: { status, code, message, retryable }, trace_id } — with retryable: false on every auth failure. The message is generic; the crypto-step detail (which header was missing, which verification step failed) is logged server-side under trace_id and is no longer leaked in the response. See Errors for the full table.
POST /api/v1/send is a legacy-shaped endpoint and still emits a flat { error, code: "auth_failed" } body for auth failures rather than the numeric envelope above. All other /api/v1/* routes use the distinct numeric codes.Rotating the signing secret
Thesigning_secret is shown exactly once at registration. To rotate it, contact your Chert administrator. Re-registering the same email returns the existing tenant and does not regenerate the secret.
Webhook signatures
Inbound webhook events are signed independently with the webhook subscriptionsecret (returned once when the subscription is created). Two signature header formats are emitted simultaneously:
Both compute the same HMAC:
HMAC-SHA256(subscription_secret, "<ts>.<raw_body>"). The raw body is the exact bytes received — do not parse and re-serialize before verifying. See Receiving replies for the consumer-side verification snippet that handles both formats.
Additional headers on every webhook delivery:

