Webhooks

DocumentationWebhooks

Simple mode on — some technical details are condensed. Switch to Dev in the nav for full API reference.

Documentation

Webhooks

Subscribe to verification, agent, and permission events. BehalfID signs each event and delivers through a durable outbox.

What webhooks are for

Webhooks push signed events to your HTTPS endpoint when agents, permissions, or verification decisions change. Use them to sync SIEM tools, open tickets on denials, pause CI when production deploy approval is required, or mirror audit activity into your own store.

Events are written to an outbox before the API response returns. Delivery runs asynchronously via /api/webhooks/process, so a down receiver does not block verify() or permission mutations.

Event types

verification.allowedverification.deniedagent.createdagent.disabledagent.enabledagent.key_rotatedpermission.createdpermission.revoked

Subscribe to a subset when you create the endpoint in the dashboard webhooks page. Only subscribed types are delivered.

Create an endpoint

  1. Open Dashboard → Webhooks and add an HTTPS URL.
  2. Select the event types you want to receive.
  3. Copy the one-time signing secret (whsec_…). BehalfID stores only a derived hash and a short preview — the full secret cannot be viewed again.
  4. Store the secret as BEHALFID_WEBHOOK_SECRET (or equivalent) in your receiver environment.

Production URLs must use https://. Local http://localhost endpoints are allowed only in development. Rotating the secret immediately stops the previous secret from verifying new deliveries.

Payload

event.json
{
  "eventId": "evt_xxx",
  "type": "verification.allowed",
  "createdAt": "2026-05-02T00:00:00.000Z",
  "accountId": "acct_xxx",
  "data": {
    "requestId": "req_xxx",
    "agentId": "agent_xxx",
    "action": "access_data",
    "allowed": true,
    "risk": "low",
    "permissionId": "perm_xxx"
  }
}

Payloads never include API keys, setup tokens, webhook secrets, or newly rotated agent keys. Treat eventId as the dedupe key.

Headers

  • BehalfID-Event-ID — stable event ID (same as payload eventId).
  • BehalfID-Timestamp — Unix seconds included in the HMAC base string.
  • BehalfID-Signaturev1=<hex_hmac> over timestamp.rawBody.

Verify against the exact raw JSON body your server received. Do not re-serialize parsed JSON before checking the signature — whitespace and key order must match.

Verify with the SDK

receiver.ts
import { verifyWebhookSignature } from "@behalfid/sdk";

export async function POST(request: Request) {
  const rawBody = await request.text();
  const valid = await verifyWebhookSignature({
    secret: process.env.BEHALFID_WEBHOOK_SECRET!,
    payload: rawBody,
    timestamp: request.headers.get("behalfid-timestamp") ?? undefined,
    signature: request.headers.get("behalfid-signature") ?? undefined
  });

  if (!valid) {
    return new Response("Invalid signature", { status: 401 });
  }

  const event = JSON.parse(rawBody) as { eventId: string; type: string };
  // Deduplicate by event.eventId, then handle side effects idempotently.
  return new Response("ok");
}

The helper rejects timestamps outside a 300-second skew window by default (toleranceSeconds). If your deployment sets BEHALFID_WEBHOOK_SIGNING_PEPPER, pass the same value as signingPepper to the SDK helper.

Retries, DLQ, and replay

Delivery is at least once. Failed deliveries retry with bounded exponential backoff, then move to a dead-letter state:

retry schedule
attempt 1 → immediate
attempt 2 → +5 seconds
attempt 3 → +30 seconds
attempt 4 → +2 minutes
attempt 5 → +10 minutes
(after 5 failures → deadLetter = true)

Inspect failed events and delivery attempts from the webhook detail page in the dashboard. After fixing the receiver, replay a dead-lettered event — replay resets status to pending, clears lastError, and sets attempts back to zero. Events that are still pending, processing, or completed cannot be replayed.

Local testing

terminal
npm --prefix examples/webhook-receiver install
BEHALFID_WEBHOOK_SECRET=whsec_xxx npm --prefix examples/webhook-receiver start

Point a development endpoint at http://localhost:4000, trigger a verification, then process the outbox (hosted deployments usually schedule /api/webhooks/process via cron). See also the SDK webhook helper and the Concepts page for how verification decisions relate to these events.