Setup

Create an endpoint

Open Settings → Webhooks and click New webhook. You'll choose:

  • Name — how the endpoint appears in the list.
  • TargetChannel post (deliver into a channel) or Workflow trigger (start a workflow). For a channel post, pick the destination channel; for a workflow trigger, pick the workflow.
  • Verifier scheme — how incoming requests are authenticated. This depends on the target: channel posts always use the generic Shared secret (header token) scheme, so there's nothing to choose; workflow triggers let you pick the signing scheme that matches whoever sends the events (see Configuration).

Creating a webhook requires the right workspace role. If you can't create or view webhooks, ask a workspace admin.

Copy the URL and secret

After the endpoint is created, open it to copy:

  • Endpoint URL — where the sender POSTs events. It encodes the workspace and endpoint id, so it's specific to this endpoint.
  • Signing secret — a whsec_… value used to sign requests. It's shown once at creation; store it in your sender's secret manager. You can rotate it later.

Send a signed request

This section covers the signing schemes used by workflow triggers. Channel-post endpoints don't sign — the sender presents the secret as a static Authorization: Bearer header instead; see Channel posts.

The sender computes a signature over the raw request body with the secret, and sends it in the header the verifier expects. Two common schemes:

TAP v1 (default)

Header TAP-Signature: t=<unix_seconds>,v1=<signature>, where the signature is HMAC-SHA256 over "<t>." + rawBody (the timestamp, a dot, then the body). The timestamp is checked against a tolerance window to reject replays.

SECRET='whsec_…'
BODY='{"event":"deploy.succeeded","service":"api"}'
T=$(date +%s)
SIG=$(printf '%s' "$T.$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

curl -X POST "$WEBHOOK_URL" \
  -H 'content-type: application/json' \
  -H "TAP-Signature: t=$T,v1=$SIG" \
  --data "$BODY"

Generic HMAC-SHA256

Header X-Signature: <signature>, where the signature is HMAC-SHA256 over the raw body (no timestamp). Use this for a custom sender that just needs a shared HMAC.

SECRET='whsec_…'
BODY='{"event":"deploy.succeeded"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

curl -X POST "$WEBHOOK_URL" \
  -H 'content-type: application/json' \
  -H "X-Signature: $SIG" \
  --data "$BODY"

For provider schemes (Stripe, GitHub, Svix), you don't compute the signature yourself — the provider signs it. Configure the provider's webhook with the endpoint URL and its signing secret, and select the matching verifier.

Verify it arrived

A verified request returns 202 (accepted). For a channel post target, the message appears in the destination channel authored by the webhook. For a workflow trigger, a new workflow run starts with the payload as input.

If a request is rejected, the signature didn't verify — confirm the sender signs the exact raw body with the right secret and uses the header the selected verifier expects.