Required headers
Building the signature
Sign a string of five lines joined by newlines:- Serialise your payload to a string. Keep that exact string — you will both hash it and send it.
- Hash it with SHA-256 and hex-encode the digest, lowercase. That is
BODY_SHA256. - Build the five-line string above.
- Base64-decode your secret key.
- HMAC-SHA256 the five-line string with the decoded secret, and hex-encode the result, lowercase.
Rules that trip people up
- Method uppercase and exact:
POST,GET. - Path exact and complete —
/api/v1/api_partner/orders. Include the/apiprefix; exclude the domain, the protocol, and any query string. - Timestamp in seconds, not milliseconds, and within 5 minutes of our clock in either direction.
- Newlines must be real newline characters, not the two literal characters
\andn. - Secret key is Base64 and must be decoded before use — unlike the webhook signing secret, which is not. See Two secrets, two conventions.
Two secrets, two conventions
You are issued two signing secrets. They point in opposite directions and are used differently, and mixing them up produces a signature that never matches with nothing in the error to say why.
Either can be replaced without affecting the other. This page covers the first
column; the second is covered under Verifying the
signature.
Examples
All four use the same request body. Substitute the fixed credentials and timestamp from the test vector and any of them will produce the signature shown there.Test vector
Fixed values to check your implementation against. Useful before you have credentials, and for confirming a change to your signing code did not alter the result.What we check
On every request, in this order:- All three headers are present.
- The timestamp is within 5 minutes of our clock. Generate it fresh per
request and keep your server on NTP — clock drift is the usual cause of
Request timestamp is too old or invalid. - We rebuild the signature from your secret key and compare it in constant time.
Errors
Authentication failures return401 Unauthorized:
"Missing required authentication headers."- One or more required headers (X-Esim-Story-Access-Key,X-Esim-Story-Signature,X-Esim-Story-Timestamp) are missing"Request timestamp is too old or invalid."- Timestamp is outside the allowed time window or invalid format"Invalid or missing access key. Please provide a valid X-Esim-Story-Access-Key header."- Access key is incorrect or partner not found"Missing secret key in partner record."- Partner exists but has no secret key configured"Invalid signature."- Signature verification failed. In order of likelihood:- The body sent differs from the body hashed. Re-serialising the payload for the request produces different bytes than the string you hashed. Serialise once, hash that string, send that string.
- Raw string built wrong — wrong line order, a literal
\ninstead of a newline, or the/apiprefix missing from the path - Secret key not Base64-decoded before use
- Body omitted from the raw string entirely (the digest line is required even when the body is empty)