Skip to content

Authentication

Every request carries three headers. There is no JWT/OAuth — the signature is the authentication.

HeaderValue
X-Api-KeyYour apiKeyId.
X-TimestampCurrent Unix time in milliseconds, as a string. Must be within ±5 minutes of our server clock.
X-SignatureLowercase hex of HMAC-SHA256(secret, "{X-Timestamp}.{signedBody}").

The signed string is the timestamp, a literal dot (.), then the signed body:

  • Award (POST /v1/ep/webhook): signedBody = the exact raw request body bytes you send.
  • Status (GET …/transactions/{orderId}): signedBody = the orderId path segment (no body).
  • History (GET …/awards): signedBody = the tenantId query value (limit and cursor are not signed).
X-Signature = hex( HMAC_SHA256( secret, X-Timestamp + "." + signedBody ) )
  • The timestamp window (±5 min) plus the per-orderId idempotency key together block replay attacks. Keep your server clock synced with NTP.
  • Signatures are compared in constant time. A wrong/short/non-hex signature is rejected.
  • An unknown apiKeyId returns the same 401 INVALID_SIGNATURE as a bad signature — we don’t reveal whether a key exists.

Ready-to-run signing code for each endpoint is on Signing & code examples.

During a rotation you may be issued a secondary secret with an expiry. We accept a signature made with either the primary or the live secondary until the secondary expires. Switch your signing key to the new secret before the window closes; no downtime is required.