Skip to main content
Webhooks push what happened in BuzzKit to a URL you own. One endpoint receives both ledgers BuzzKit keeps: the control-plane audit log, which records what people and keys changed, and the subscriber event stream, which records what happened to a subscriber. Both are filtered by name, signed the same way and retried on the same schedule. Endpoints belong to the workspace, so the routes are workspace-context and take the workspace slug in the path. webhooks:read is member-level and webhooks:write is admin-level. Tenant keys are refused, though an endpoint can filter down to a single tenant.

Creating an endpoint

The response is the only time the signing secret is returned in full at creation. tenant is optional and narrows the endpoint to one tenant. A workspace can hold at most 50 endpoints. In production the URL has to be https and publicly routable, and a URL that carries credentials or points at a private address is a 400 invalid_url.

Choosing events

events takes exact names, resource.* patterns, * for everything public, or your own event names such as order.completed and order.*. Leave it out and the endpoint receives every public event.
The catalog returns the subscribable BuzzKit events grouped by resource, which is what the dashboard’s picker renders. Workflow runs are stream events like any other: subscribe to $run.* and every run reaches your endpoint with its runId, workflow and versionId. Private audit names such as key.*, webhook.* and profile.* can never be subscribed, and asking for one is a 400 invalid_event.
An endpoint only receives what happened after it existed. Creating, editing or re-enabling an endpoint never back-delivers events from before that change.

The payload

Every delivery carries an immutable event object with its own id, not a bare notification. The payload is built once and stored, so retries and replays re-send the exact same snapshot.
Control-plane events add actor, target and request, and *.updated events carry changes and previousAttributes. To read an event back from the API instead of trusting the body, GET /v1/workspaces/:slug/webhooks/events/:id returns the same object. Ordering is not promised. Dedupe on the webhook-id header and order on createdAt, or on the sequence inside data.object for stream events.

Verifying the signature

Signing follows Standard Webhooks. Every request carries webhook-id (the event id, stable across retries), webhook-timestamp (unix seconds) and webhook-signature (v1,<base64 HMAC-SHA256 over "id.timestamp.body">). The buzzkit package ships the check.
verifyWebhook compares in constant time and rejects anything older than five minutes. It accepts an array of secrets while you are rotating. Read the raw body before parsing it, verify, then dedupe on id. To verify by hand, concatenate webhook-id, webhook-timestamp and the raw body with dots, take the HMAC-SHA256 of that string with the secret, base64 the result, and compare it in constant time against the value after v1,. The header can hold several space-separated signatures during a rotation, so treat a match on any one of them as valid, and reject a timestamp that is far from now.
Verify against the exact bytes you received. Parsing the JSON and re-serializing it changes the body and the signature will not match.

Deliveries and retries

Every delivery is a row you can read, and every attempt underneath it records its status, error, duration and the first 4 KB of the response.
The list is newest first and takes status as pending, success, failed or exhausted. GET /v1/workspaces/:slug/webhooks/:id/deliveries/:deliveryId returns one delivery with every attempt and the event it carried. A 2xx is a success. Anything else is a failed attempt: a non-2xx status, a timeout after 30 seconds, or a network error. Redirects are not followed, so a 3xx counts as a failure like any other non-2xx. Retries run at 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and then every 12 hours, ten attempts in all over about three days, after which the delivery is exhausted. An endpoint that has been failing continuously for three days is disabled. Any success resets the streak. Re-enable it with PATCH { "enabled": true }, which clears the failure streak and re-enqueues the deliveries that were left pending or failed, so recovery does not wait.

Replaying

A replay answers 202 and re-sends the stored payload as one more attempt. Replaying against a disabled endpoint is a 400 endpoint_disabled, so re-enable the endpoint first.

Rotating the secret

You get a new whsec_ secret. The previous one keeps verifying for 24 hours and both signatures are sent on every request during that window. Pass both secrets to verifyWebhook until the old one expires.

Next

Events

The subscriber event stream that feeds $ webhook events.

Tenants

Filtering an endpoint down to a single tenant.

Authentication

The scopes an endpoint’s routes require.