On this page

Webhooks

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.

EventSent when
project.created / project.ready / project.failedA project was created / is active (and its template built) / could not be set up
project.paused / project.restoredPaused (API or inactivity) / restored
project.deleted / project.expiredDeleted / its TTL ran out (then deleted)
project.claimedA customer claimed the project into their organization
usage.thresholdDatabase size, file storage or end users crossed one of your percentages of the (capped) limit
limit.reachedA hard limit took effect (writes blocked, uploads blocked, sign-ups refused)
backup.completed / backup.failedA project backup finished / failed
function.deployedA new Edge Function version is live
member.added / member.removedOrganization membership changed

#Payload and signature

JSON
{
  "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.

JavaScript
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.