Webhooks

Subscribe to connection, action and approval events, verify the signature, and handle retries and failing endpoints.

Webhooks push events to your endpoint instead of you polling the audit log for changes. Add one from the Webhooks page.

The Webhooks page listing endpoints with their status, event subscriptions and last delivery

Event types

EventFires when
connection.createdA user completes a connection
connection.expiredA connection's refresh token dies and it needs reconnecting
connection.revokedA connection is revoked, by the user or from the dashboard
action.blockedA policy denies a call outright
action.failedAn upstream call errors
approval.requestedAn ask rule holds a call
approval.decidedAn approval is approved, rejected or expires
user.createdA new connected user is created

Subscribe to approval.requested and approval.decided if your app needs to act on approvals from code rather than waiting on the approver's email; see Approvals.

Payload

Every delivery carries the same envelope, with the event-specific fields under data:

Request body
{
  "id": "wev_01k61z9m4c5f0rce9dxyt3h7qb",
  "type": "connection.expired",
  "created": "2026-09-27T14:02:11Z",
  "project": "prj_desk",
  "environment": "production",
  "data": {
    "id": "conn_z663h86fl3z2",
    "app": "salesforce",
    "user": "u_8f2",
    "status": "expired",
    "status_reason": "invalid_grant: token has been expired or revoked"
  }
}

Verifying the signature

Every request carries a signature over the raw body. Verify it before trusting the payload — an unverified endpoint is a public write API to anyone who finds the URL.

Arc0-Signature: t=1758981600,v1=5257a869e7...

t is a Unix timestamp and v1 is the hex HMAC-SHA256 of {t}.{body}, computed with your endpoint's signing secret (whsec_••••••••{last4}, shown under Reveal).

verify.ts
import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody: string, header: string, secret: string): boolean {
  const [tPart, vPart] = header.split(',');
  const timestamp = tPart.split('=')[1];
  const signature = vPart.split('=')[1];

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const fresh = Date.now() / 1000 - Number(timestamp) < 300; // 5 minutes

  return (
    fresh &&
    timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
  );
}

Verify against the raw request body, before any JSON parsing. Re-serializing the body changes whitespace and key order, and the signature will never match.

Retries and failing endpoints

A delivery that doesn't return a 2xx is retried up to 5 attempts, backing off: 0, 1 minute, 10 minutes, 1 hour, then 6 hours. The delivery's Next retry column shows a time, Queued, or Gave up once attempts run out.

An endpoint that keeps failing is marked Failing, showing the status since it started — "HTTP 500 since ..." — and stops receiving new deliveries until you fix it. Left long enough, it moves to Disabled.

A failing webhook endpoint, showing the HTTP 500 status and time it started failing

Return your 2xx quickly and do the real work after — acknowledge first. Retries mean a delivery can arrive more than once, so key your handling on id, not on delivery count.

Delivery detail

Every attempt is logged, with the request body, headers, response and duration:

A webhook delivery's detail view: request body, headers, response and attempt number
Attempt 2 of 5, with the response Arc0 received back.

Test events

Use Send test event on any endpoint to fire a sample payload without waiting for a real connection or call to trigger one — useful for checking your signature verification before you go live.

On this page