Webhooks

Set the URL under Settings → Webhook. Choose which events it receives, and send a test event from there.

Events

TypeWhen
trigger.messageA trigger fired. data is the event, metadata names the trigger, account and user.
connected_account.createdA connection became active.
connected_account.expiredA token could not be refreshed.
webhook.testYou pressed "Send test event".
{
  "id": "evt_...",
  "type": "trigger.message",
  "metadata": { "trigger_slug": "GITHUB_PUSH_EVENT", "trigger_id": "ti_...", "connected_account_id": "ca_...", "user_id": "alice", "toolkit": "github" },
  "data": { "...": "..." },
  "timestamp": "2026-11-06T10:00:00.000Z"
}

Verify the signature

Deliveries follow the Standard Webhooks format. Each has three headers: webhook-id, webhook-timestamp (unix seconds) and webhook-signature (v1, followed by a base64 HMAC-SHA256). Sign id.timestamp.body with your webhook secret and compare.

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

export function verify(secret: string, headers: Headers, body: string) {
  const id = headers.get("webhook-id")!
  const ts = headers.get("webhook-timestamp")!
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
  const expected = "v1," + createHmac("sha256", secret).update(`${id}.${ts}.${body}`).digest("base64")
  const given = headers.get("webhook-signature") ?? ""
  return given.split(" ").some((sig) => sig.length === expected.length && timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
}

Answer with any 2xx status within 10 seconds. Every attempt is listed under Settings → Webhook → Deliveries.