Webhooks

Push interrupt and session lifecycle events to your systems instead of polling.

Webhooks push interrupt and session lifecycle events to your infrastructure the moment they happen: an agent opens an interrupt, a human decides in the inbox, and your endpoint is called - no polling loop required. The typical uses are resuming a paused agent run the instant its interrupt is answered, and reacting when an operator steps into a Live View session.

Events

EventSent when
interrupt.createdAn agent opened a new interrupt.
interrupt.answeredEvery action request on an interrupt was decided.
interrupt.escalatedAn interrupt was raised to the manager review queue.
session.startedAn agent registered a live session.
session.endedA live session ended - by the agent, a watcher, or the session timeout.
session.actionA watcher fired a custom action against a live session.

Every payload shares the same envelope - type, timestamp and data. timestamp is the moment the lifecycle edge happened, not the delivery time. Payload shapes are a stable public contract: fields may be added, but existing ones will not change shape.

Interrupt events

{
  "type": "interrupt.created",
  "timestamp": "2025-09-10T08:03:12Z",
  "data": {
    "id": "8f14e45f-ceea-4672-8657-a1b2c3d4e5f6",
    "externalId": "run_42",
    "title": "Send onboarding email",
    "description": "The agent wants to email a new customer.",
    "classification": "billing",
    "actionRequests": [
      {
        "name": "send_email",
        "args": { "to": "customer@example.com" },
        "allowedDecisions": ["approve", "edit", "reject"]
      }
    ]
  }
}

In interrupt.answered, each action request carries the reviewer's decision (approve, edit, reject or respond), who made it (decidedByName), the replacement arguments for an edit (editedArgs), and responseText - the answer for a respond, or an optional reason for a reject. externalId is the correlation id you supplied when opening the interrupt (for example your agent run id), so you can route the event back to the right run.

Session events

{
  "type": "session.started",
  "timestamp": "2025-09-10T08:00:00Z",
  "data": {
    "id": "3c9d2b7a-1f0e-4b6a-9d21-abcdefabcdef",
    "externalId": "thread_42",
    "name": "billing-agent",
    "startedAt": "2025-09-10T08:00:00Z"
  }
}

Session events are how Live View talks back to your agent. Note that there is no session.escalated event - escalating a session only changes who is watching it in the dashboard. name is the agent's name as registered with the session, and externalId the correlation id you supplied when starting it. In session.ended, reason tells you how the session closed:

ReasonMeaning
agentThe agent ended the session itself when its run completed.
timeoutThe session went quiet past your organisation's session timeout and was closed automatically - the agent may have crashed.
manualA watcher disconnected the session from Live View.

In session.action, action is the name of the custom action a watcher pressed (defined under Integrations → Live View) and triggeredBy who pressed it. What an action means - pause, hand off, dump state - is entirely up to your agent side. Because action carries the name rather than an id, renaming an action in the dashboard changes what your handler receives.

Creating an endpoint

Add the endpoint

In the dashboard, open your organisation's Integrations page and switch to the Webhooks tab. Press Add endpoint and give it the URL to deliver to (it must start with https://), an optional description, and the event types it should receive - at least one; subscribe to all of them or just the ones you handle. Editing an endpoint later applies to future deliveries only.

Webhooks are managed by signed-in members, not API keys. Seeing the endpoints and their deliveries needs integrations.view; adding one needs integrations.create; editing, enabling or disabling, viewing or rotating the secret, test events, resends and recovery need integrations.update; deleting needs integrations.delete.

Copy the signing secret

Each endpoint has its own signing secret (whsec_...). Open it from Signing secret in the endpoint's actions menu - on its row in the list or on its detail view - and store it alongside the service that receives the deliveries; you will use it to verify that requests really come from Vigilator.

The same dialog has Rotate secret. After a rotation the previous secret keeps working for 24 hours, so you can roll the new one out to your receiver without dropping deliveries in between.

Send a test event

From the endpoint's detail view, Send test event delivers an example payload for any event type the endpoint subscribes to - the payload preview in the dialog is exactly what will be sent. Use it to confirm your receiver responds with a 2xx before wiring it into anything real.

Verifying deliveries

Deliveries are signed following the Standard Webhooks specification (via Svix). Every request carries three headers:

HeaderPurpose
svix-idUnique message id. Stable across retries - use it to deduplicate.
svix-timestampUnix timestamp of the delivery attempt. Reject stale values to prevent replays.
svix-signatureOne or more versioned signatures, space-delimited.

The signature is an HMAC-SHA256 over {svix-id}.{svix-timestamp}.{raw body}, keyed with the portion of the secret after the whsec_ prefix (base64-decoded). Any Svix or Standard Webhooks library can verify it - or let the Python SDK do it for you:

from vigilator_py_sdk import WebhookHandler

webhooks = WebhookHandler(secret="whsec_...")
event = webhooks.construct_event(raw_body, headers)  # verifies, then parses

Verify the raw body

The signature covers the request body exactly as it arrived. Parsing the JSON and re-serializing it produces different bytes and a different signature - always verify against the raw bytes, before anything touches them.

Delivery behaviour

Respond fast with a 2xx. Any other status - or a timeout - counts as a failed attempt. Acknowledge first and offload slow work to a queue or background task.

Failed deliveries are retried automatically on an exponential backoff schedule. Each delivery's attempt trail - every attempt with its status code and time - is visible under Recent deliveries on the endpoint's detail view, where a delivery shows as Succeeded, Pending or Failed. A failed delivery can be resent by hand with Resend, and Recover failed messages… re-sends every failed delivery from the last hour, last 24 hours or last 7 days - useful after an outage or a misconfigured endpoint.

Deliveries are idempotent at the source. Each lifecycle edge of an interrupt or session produces exactly one message with a fixed id, so retries on our side can never deliver a new duplicate. Retries of the same message reuse the same svix-id, so if your endpoint acknowledged an attempt that later timed out on our side, deduplicating on svix-id makes redelivery harmless.

Ordering is not guaranteed. Retries mean an interrupt.answered delivery can arrive after a redelivered interrupt.created, or a session.ended before a session.started. Use the payload's timestamp when order matters.

Failing endpoints are flagged, then disabled. An endpoint whose most recent attempt failed is marked Failing in the list, and its detail view shows its error rate over the last 7 days. When deliveries run out of retries the organisation's owner is emailed (at most once an hour per endpoint), and an endpoint that keeps failing is eventually disabled automatically, with another email. Re-enable it from its actions menu once your receiver is healthy, then recover the missed deliveries.

Endpoints can be paused. Disable stops deliveries to an endpoint without deleting it - handy during a receiver migration - and Enable resumes them. Deleting an endpoint stops deliveries immediately and cannot be undone.

Testing locally

Your endpoint URL must be reachable from the internet, which local dev servers are not. Two easy ways around that:

Svix CLI - relays deliveries to localhost without deploying anything:

svix listen http://localhost:8000/webhooks/vigilator

The command prints a public relay URL - use it as the endpoint URL in the dashboard, and deliveries are forwarded to your local server.

Cloudflare Tunnel - exposes your local server on a public URL:

cloudflared tunnel --url http://localhost:8000

Use the printed trycloudflare.com URL (plus your webhook path) as the endpoint URL. ngrok and similar tunnels work the same way.

Whichever you use, combine it with the dashboard's send test event to exercise your receiver end to end - including signature verification, since test events are signed like real ones.

On this page