Before you begin
- Get a key pair for each environment. Staging and production each need their own.
- Generate the pair yourself and send Bilt only the
whpk_public key. Or ask Bilt to generate it: Bilt shows the private key once and does not store it. - Send the public key to your Bilt integration manager. Bilt returns a key ID of the form
bk_<environment>_<22 letters and digits>. - Keep the
whsk_private key in a secret manager on your servers. Never send it to Bilt.
whsk_ plus the base64 of the 32-byte Ed25519 seed. The public key is whpk_ plus the base64 of the 32-byte Ed25519 public key. These snippets generate a pair:
How it works
Bilt follows the Standard Webhooks specification with its asymmetricv1a scheme. You sign {webhook-id}.{webhook-timestamp}.{raw request body} with Ed25519 and send the signature in headers. Bilt answers 202 first, then checks the signature with your public key shortly after.
Steps
1
Serialize the body once
Serialize the event to a JSON string. Sign that exact string and send the same bytes. Encoding the object again after signing breaks the signature.
2
Pick the webhook-id
Use a new ID, such as a UUID, for each new request. Reuse the same ID when you retry the same body. A batch is one request with one
webhook-id.3
Take the timestamp
Use the current Unix time in integer seconds as
webhook-timestamp. Keep your server clock in sync.4
Sign
Sign
{webhook-id}.{webhook-timestamp}.{body} with your private key. The official standardwebhooks libraries only support the symmetric v1 scheme today, so these examples use each language’s own Ed25519 support:5
Send the headers
Send
webhook-id, webhook-timestamp, webhook-signature (v1a, plus the base64 64-byte signature), X-Bilt-Key-Id, and Content-Type: application/json. On a retry, keep the ID and body and sign again with the current time.6
Check your code
Ed25519 signatures are deterministic. Sign these inputs with the published example key, and never use it for real requests:
You should get this header:
7
Rotate or revoke your key
To rotate, send Bilt a new public key and switch to the new key ID. The old key keeps working for 7 days, then Bilt rejects it. During rotation you can send one
v1a, entry per key, space-separated. Any one valid entry passes, and other schemes such as v1, are ignored.If a private key may have leaked, ask Bilt to revoke it. Revocation is immediate. Register a new key to keep sending. Scheduled rotation is optional.What can go wrong
A request that fails the signature check gets no error response, because Bilt already sent the202. Bilt does not process it or retry it. Ask your integration manager for the reason, giving the webhook-id. A self-serve lookup is planned.
unknown_key:X-Bilt-Key-Idis not a live key for you. It was never registered, was revoked, is past its rotation window, or your integration is not enabled yet. See Errors and retries.stale_timestamp:webhook-timestampis more than 300 seconds from when Bilt received the request, in either direction. See Errors and retries.bad_signature: nov1a,entry is a valid signature for that key. The usual cause is signing different bytes than you sent. See Errors and retries.400right away: a signing header is missing, longer than 1,024 bytes, or the timestamp is not an integer. See Report an order event.
Next steps
- Send order events covers what goes in the body.
- Handle rejections and resends covers retries.