Signed HTTPS events for project lifecycle, usage thresholds, limits, backups, deploys and members, with retries and a delivery log.
Add endpoints in Organization › Webhooks or with POST /organizations/<org>/webhooks { url, events: ["*"], usage_thresholds: [80, 100] }. The signing secret (whsec_…) is shown once.
| Event | Sent when |
|---|---|
project.created / project.ready / project.failed | A project was created / is active (and its template built) / could not be set up |
project.paused / project.restored | Paused (API or inactivity) / restored |
project.deleted / project.expired | Deleted / its TTL ran out (then deleted) |
project.claimed | A customer claimed the project into their organization |
usage.threshold | Database size, file storage or end users crossed one of your percentages of the (capped) limit |
limit.reached | A hard limit took effect (writes blocked, uploads blocked, sign-ups refused) |
backup.completed / backup.failed | A project backup finished / failed |
function.deployed | A new Edge Function version is live |
member.added / member.removed | Organization membership changed |
#Payload and signature
{
"id": "1d0c…",
"type": "project.ready",
"created_at": "2026-10-19T12:00:00.000Z",
"organization_id": "…",
"data": { "project": { "ref": "abcdefghijklmnop", "name": "Acme", "labels": { "customer_id": "cus_123" } } }
}Headers: StitchDB-Webhook-Id (the event id, use it to ignore duplicates), StitchDB-Webhook-Timestamp (Unix seconds), StitchDB-Event, and StitchDB-Signature: v1=<hex> = HMAC-SHA256 of <timestamp>.<raw body> with your secret.
import crypto from 'node:crypto'
export function verify(secret, req, rawBody) {
const ts = req.headers['stitchdb-webhook-timestamp']
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false // older than 5 minutes: replay
const want = 'v1=' + crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex')
const got = String(req.headers['stitchdb-signature'] || '')
return got.split(',').some((s) => s.trim().length === want.length && crypto.timingSafeEqual(Buffer.from(s.trim()), Buffer.from(want)))
}#Delivery
HTTPS to public addresses only, 10 s timeout, no redirects. Any 2xx counts as delivered. Otherwise retries after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h; an endpoint failing for 3 days is disabled (enable it again in the dashboard). The delivery log (30 days) shows every attempt; redeliver from there or with POST …/deliveries/<id>/redeliver. POST …/test sends a test.ping right away.