Skip to main content
Bilt accepts an order report only when you sign it with a private key that only you hold.

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.
The private key is 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 asymmetric v1a 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 the 202. 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-Id is 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-timestamp is more than 300 seconds from when Bilt received the request, in either direction. See Errors and retries.
  • bad_signature: no v1a, entry is a valid signature for that key. The usual cause is signing different bytes than you sent. See Errors and retries.
  • 400 right away: a signing header is missing, longer than 1,024 bytes, or the timestamp is not an integer. See Report an order event.
Fix the cause and send the request again. It was never processed.

Next steps