Skip to content

Developers

Webhooks

Get events pushed to your own endpoint, and verify they came from us.

2 minute read


Rather than polling the API, have us call you. Settings → Webhooks is where endpoints live.

Setting one up

Add the URL, choose which events you want, and save. You are shown a signing secret once; store it. You can subscribe to individual event types or to everything with *.

Available events include member changes, invitations, file uploads and deletions, API key changes, subscription updates, paid orders, credits being added (credits.granted: a pack bought, an automatic top-up, an adjustment) or running low (credits.low), and every audit log entry.

Verifying a delivery

Each request carries three headers:

webhook-id: evt_01H...
webhook-timestamp: 1788780000
webhook-signature: v1,base64signature

The signature is an HMAC-SHA256 over {id}.{timestamp}.{body} using your endpoint's secret, base64 encoded. Verify it before trusting anything, and compare in constant time:

import { createHmac, timingSafeEqual } from 'node:crypto'

function verify(headers, rawBody, secret) {
  const id = headers['webhook-id']
  const timestamp = headers['webhook-timestamp']
  const expected = createHmac('sha256', secret)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest('base64')
  const sent = headers['webhook-signature'].split(',')[1]
  const a = Buffer.from(expected)
  const b = Buffer.from(sent)
  return a.length === b.length && timingSafeEqual(a, b)
}

Use the raw request body, exactly as received. Parsing and re-serialising it changes the bytes and the signature will not match.

Also reject deliveries whose timestamp is more than a few minutes old, so an intercepted request cannot be replayed later.

Responding

Answer 2xx quickly and do the real work afterwards. A non-2xx response, or a timeout, is retried with exponential backoff. If your endpoint is down for a while, deliveries queue and catch up rather than being lost.

Deliveries can arrive more than once and out of order. Use webhook-id to discard duplicates, and treat each payload as the current state rather than assuming you saw the previous one.

When something goes wrong

The webhooks page lists recent deliveries with their status code and response body, and has a Redeliver button for any of them. That is the fastest way to test a fix without waiting for the next real event. Send test delivers a ping event on demand.

If you need to rotate a leaked secret, do it from the same page; deliveries after that point are signed with the new one.

Did this miss something? Tell us.