Subscribe to events
Webhooks live alongside API tokens in your dashboard under API & webhooks, in the Webhooks card. Enter a target URL and choose at least one event, then click Add webhook.
The events you can subscribe to are:
code.scanned- a dynamic code was scannedcode.created- a new code was createdcode.updated- a code was changed
The target must be a public https URL - a localhost or private-network address is rejected
with "That URL isn't reachable from the public internet (no localhost or private-network targets)."
Once added, the webhook appears in a table with its URL, events, signing secret, status
(Active or Disabled), and a Delete action that stops deliveries immediately.
Verify every delivery
Each delivery is signed using the Standard Webhooks scheme, so any off-the-shelf Standard-Webhooks receiver library can verify it. Every request carries three headers:
webhook-id: <this delivery's id>
webhook-timestamp: <unix seconds>
webhook-signature: v1,<base64 HMAC-SHA256>
The signature is an HMAC-SHA256 over the string {webhook-id}.{webhook-timestamp}.{raw-body}, keyed
by your subscription's signing secret. The secret starts with whsec_ and is shown in the
webhooks table.
To verify a request:
- Recompute the HMAC-SHA256 of
{id}.{timestamp}.{body}using the secret. Important: key the HMAC on the base64-decoded bytes of the secret (the part afterwhsec_), not the raw text - that's what the Standard Webhooks spec and its libraries do. - Compare your result to the
v1,...value inwebhook-signaturein constant time. - Reject stale timestamps - if
webhook-timestampis more than 5 minutes from now, treat it as a replay and drop it.
The webhook-signature header may eventually carry several space-separated v1,... values to support
key rotation. Accept the delivery if any of them matches, and always verify against the raw
request body exactly as received (don't re-serialize the JSON first).
Retries and auto-disable
We deliver over HTTP POST with a 10-second timeout and don't follow redirects (a 3xx counts as
undelivered). A 2xx response marks the delivery as sent. Anything else is retried on a fixed
backoff:
30 seconds → 2 minutes → 10 minutes → 1 hour → 4 hours.
After those five retries are exhausted, that delivery is marked dead and not tried again. If a subscription keeps failing - 20 consecutive failed or dead deliveries - it auto-disables, its status flips to Disabled, and it stops receiving events until you fix and re-add it. Delivery history is pruned after 14 days.
Because a delivery is retried, your endpoint should be idempotent - use the webhook-id header to
recognize and ignore a delivery you've already processed. Return a 2xx quickly; do slow work after
acknowledging.
Webhooks fire on your codes' activity - create a dynamic code to start receiving code.scanned
events.
FAQ
Where do I get the signing secret?
It's shown in the webhooks table next to each subscription, and it starts with whsec_.
Why are my deliveries failing on a local URL?
Only public https URLs are accepted. localhost and private-network addresses are blocked.
How many times will you retry? Five retries after the first attempt - at 30s, 2m, 10m, 1h, and 4h - then the delivery is marked dead.
Why did my webhook turn Disabled? It auto-disables after 20 consecutive failed deliveries. Fix your endpoint, delete the disabled webhook, and add it again.