Webhooks

Managing Webhooks

Create, edit, rotate, and view deliveries from the workspace dashboard.

Webhook management lives under Webhooks in the workspace sidebar, at /webhooks. Doing it from the dashboard keeps the audit trail tied to a real human, and every write there asks for a verification code: creating, editing, deleting, rotating the secret and revealing it are all guarded, and one code covers the lot for an hour. The dashboard is not the only door, though: the same endpoints are exposed on the REST API and in the SDK to any API key carrying the webhooks scope, which is none until you grant it. The one thing an API key can never do is read an existing secret back.

Permissions

PermissionOwnerAdminViewer
webhooks:read✅✅✅
webhooks:create✅✅❌
webhooks:update✅✅❌
webhooks:delete✅✅❌
webhooks:rotate✅✅❌
webhooks:secret_read✅✅❌

Custom roles can mix and match all six via the Roles page. API keys are narrower: the webhooks scope grants webhooks:read at read level and create, update, delete and rotate at write level. webhooks:secret_read is not in that map, so reading a secret back stays a dashboard action.

Creating an endpoint

  1. Open Webhooks, then click Create webhook. Pressing c anywhere on that page opens the same modal.
  2. Fill in name, endpoint URL, optional description, and at least one event type.
  3. Save. Unless you verified in the last hour, the code dialog opens here, and the create is rerun for you once you clear it.
  4. The endpoint is enabled the moment it is created and starts collecting matching deliveries.

The URL is validated on the server and must be https. Plain http, a URL carrying a username or password, and hosts that resolve to loopback, private, link-local or shared address space are all refused, as are localhost, .internal, .local, .localdomain, .home.arpa and the cloud metadata hosts.

The dashboard event picker offers 47 of the 48 event types the API accepts. subscription.duplicate_detected is missing from it, so subscribe to that one over the REST API or an SDK. GET /webhooks/event-types always returns the full catalogue.

The signing secret

Creating an endpoint mints an ev_whsec_ secret from 24 random bytes. It is stored encrypted rather than hashed, so it stays recoverable:

  • Nothing shows the secret at creation time. The modal closes on a "Webhook created" toast, and the secret waits in the table.
  • The Secret column renders a masked ev_whsec_ chip. Clicking it asks for a verification code, then copies the plaintext secret straight to your clipboard. The value is never printed on screen.
  • Losing your copy is not a reason to rotate. Copy it again from that column.
  • The REST API is stricter. It returns the secret only in the create and rotate responses, and has no endpoint that reads it back.

Editing

The edit modal covers name, URL, description and subscribed events. Enable and disable is a separate item in the same dropdown, not a field in the modal. Both routes go through the same guarded update endpoint, so an edit and a disable each ask for a code when your hour has run out.

Edits apply per attempt, not per event. The delivery worker re-reads the endpoint row on every attempt, so a retry of a delivery that was queued before your edit goes to the new URL and is signed with the current secret.

Rotating the signing secret

  1. Open the dropdown on the webhook row and click Rotate secret.
  2. Type the endpoint name to confirm.
  3. Enter the verification code. It is a 6 character code emailed to you, or, with two factor authentication on, the 6 digits from your authenticator app and no email at all. One code elevates you for an hour and covers the other guarded actions in the workspace inside that window.
  4. The old secret stops signing immediately. Copy the new one from the Secret column and update your receiver.

Disabling vs deleting

  • Disabling stops the fan-out: new events no longer create deliveries for that endpoint. It does not park what is already queued. Those rows are marked failed on their next attempt with the error Endpoint disabled.
  • Deleting is soft. The row is flagged deleted and forced disabled, disappears from the list, and any queued deliveries fail the same way. It asks for the endpoint name plus a verification code, and there is no undo.

Viewing deliveries

The dropdown on any webhook row has View deliveries, which opens the Recent deliveries drawer, newest first. Each row shows:

  • Event type and status badge (Succeeded, Failed or Pending; queued and in-flight attempts both render as Pending)
  • HTTP response code, when the receiver answered at all
  • Attempt number and the time of the last attempt
  • Error message, when there is one
  • Resend, which needs webhooks:update

Resend does not retry the row in place. It inserts a fresh delivery for the same event with the attempt count back at zero, so the full five-attempt schedule starts over. It is refused when the endpoint is disabled or deleted, and once the workspace already holds 500 deliveries that are pending or processing.

Limits

LimitValue
Max endpoints per workspace20
Webhook name length1 to 64 characters
URL lengthup to 2048 characters, https only
Description lengthup to 200 characters
Request timeout10 seconds
Max delivery attempts5
Backoff after a failed attempt30 s, 2 min, 10 min, 30 min
Max pending or processing deliveries per workspace500
Deliveries per page15 by default, selectable up to 100

Errors the dashboard surfaces

ConditionStatus
Acting without the matching webhooks:* permission401
Create, edit, delete, rotate or secret read without a live elevation. error.code is auth.step_up_required403
Invalid name, URL, description or event type400
Editing an endpoint that was already deleted400
Creating the 21st endpoint409
Rotate, delete, secret read or deliveries on an unknown endpoint404
Resending onto a disabled or deleted endpoint404
Resending past 500 queued deliveries409