Docs / Create a webhook
Create a webhook
Register an HTTPS address that receives signed messages.
POST/v1/webhooks · API key required
Register a public HTTPS address (port 443, no IP addresses or local names). The response includes a secret that is shown only once; keep it to check the signature on every message.
Each message is a POST with a JSON body and these headers: X-RecallDB-Event (recall.added, recall.changed or test), X-RecallDB-Delivery, X-RecallDB-Timestamp (Unix seconds) and X-RecallDB-Signature, which looks like t=1763035200,v1=<hex>. The signature is the HMAC-SHA256 of the timestamp, a dot and the exact body text, keyed with your secret. Recompute it, compare it in constant time, and reject messages whose timestamp is more than 5 minutes old.
Answer with any 2xx status within 8 seconds. Redirects are not followed. Anything else is retried up to 6 times in all, waiting 1, 5, 30, 120 and 360 minutes. Use X-RecallDB-Delivery or the body id to ignore a message you already handled. A webhook that fails 30 times in a row is switched off.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
url | body | yes | The https address that receives the messages. Example: https://example.com/recalls |
Request
curl -X POST https://recalldb.app/v1/webhooks \
-H "Authorization: Bearer $RECALLDB_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/recalls"}'const res = await fetch('https://recalldb.app/v1/webhooks', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.RECALLDB_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"url": "https://example.com/recalls"}),
})
console.log(await res.json())import os, requests
r = requests.post("https://recalldb.app/v1/webhooks", headers={"Authorization": f"Bearer {os.environ['RECALLDB_KEY']}"}, json={"url": "https://example.com/recalls"})
r.raise_for_status()
body = r.json()
print(body)Response
{
"data": {
"id": "wh_3f9a1c07b2d4",
"url": "https://example.com/recalls",
"status": "active",
"secret": "whsec_9b2c...",
"secret_note": "Keep this secret. It is shown only now."
}
}Every response includes source (agency, official ID, official URL, last updated) and attribution. Attribution rules.
Notes
- Node example to check a message: compute crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex") and compare it with v1.
- Free keys can have 1 webhook. Its messages are sent together once an hour (at the top of the hour, UTC), each still as its own signed POST; delivery within minutes is a planned Pro feature.
- Counts as one request.
Errors: error codes. Limits: rate limits.