Docs menu

Docs / Create a webhook

Create a webhook

Last updated 2026-11-13

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

NameInRequiredDescription
urlbodyyesThe 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

200 OK
{
  "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.