Documentation menu
Webhooks
Webhooks push events to your systems as they happen — a scan, a suspicious event, a diversion — so you can react in real time instead of polling. Every delivery is signed and retried.
Create an endpoint
Register an HTTPS URL and the events you want. The response includes a signing secret (whsec_...), shown only once — store it to verify deliveries.
curl -X POST "https://api.getauthorigin.com/api/v1/webhook-endpoints" \
-H "X-Api-Key: $AUTHORIGIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/authorigin",
"events": ["scan.recorded", "suspicious_scan.detected"]
}'Subscribe to "*" to receive every event type.
Event types
| Event | Fires when |
|---|---|
scan.recorded | A product code was scanned. |
suspicious_scan.detected | A scan was risk-scored as suspicious (clone, impossible travel, excessive scanning). |
product.diverted | A genuine unit was scanned outside its authorized markets. |
counterfeit_report.created | A consumer reported a suspected fake. |
barcode_job.completed | A code-generation job finished. |
barcode_job.failed | A code-generation job failed. |
consignment.received | A distributor confirmed receipt of a consignment. |
consignment.recalled | A consignment was recalled. |
Delivery format
Each delivery is a POST with a JSON body and these headers:
X-AuthOrigin-Event— the event typeX-AuthOrigin-Event-Id— a stable id for the event (use it to deduplicate)X-AuthOrigin-Delivery— a unique id for this delivery attemptX-AuthOrigin-Signature— the signature (see below)
{
"id": "evt_...",
"type": "scan.recorded",
"createdAt": "2026-08-22T12:34:56Z",
"data": {
"scanId": "scan_...",
"productUnitId": "unit_...",
"result": "genuine",
"country": "US"
}
}Verifying signatures
AuthOrigin signs every delivery so you can confirm it's genuine and unmodified. The scheme mirrors the widely-used Stripe convention, so off-the-shelf code works. The header looks like:
t=1755865200,v1=5257a869e7ecebeda32affa62cdca3fa...To verify, compute an HMAC-SHA256 over "{t}.{raw body}"using your endpoint's signing secret, then compare it to v1 in constant time. Reject deliveries whose timestamp is too old to prevent replays.
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const ok = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1),
);
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return ok && fresh;
}Retries & failures
Respond with a 2xx status within a few seconds to acknowledge a delivery. Any other response (or a timeout) is treated as a failure and retried with exponential backoff. Endpoints that keep failing are backed off automatically; inspect recent attempts at GET /webhook-endpoints/{id}/deliveries.
AuthOrigin