Webhooks

Webhooks let Anvil push events to your systems in real time, so you can react to new leads, conversation updates, and exports without polling.

What Webhooks Do

Whenever something interesting happens in your tenant (a new lead scoring above 80, a finished AI conversation, a completed bulk export), Anvil POSTs a JSON payload to a URL you control. You typically use webhooks to:

  • Create records in your CRM the moment a lead arrives.
  • Kick off a Slack alert when a high-intent prospect is detected.
  • Trigger downstream pipelines in a data warehouse when exports finish.
  • Creating a Webhook

  • Go to **Settings > Webhooks**.
  • Click **Add Endpoint**.
  • Enter the full HTTPS URL that should receive events.
  • Select the events to subscribe to (multi-select).
  • Copy the generated **signing secret** — you will need it for verification.
  • You can create up to 20 webhook endpoints per tenant on Pro, unlimited on Enterprise.

    Event Types

    EventWhen it fires
    `lead.created`A new lead enters your pipeline
    `lead.score_changed`The intent score crosses a configured threshold
    `lead.stage_changed`A lead moves between pipeline stages
    `conversation.started`A bot session begins
    `conversation.handoff`A bot requests human takeover
    `conversation.ended`A session closes
    `crawl_task.completed`A scheduled crawl finishes
    `export.ready`A bulk export is available for download

    Payload Format

    All webhook payloads share the same envelope:

    {
      "id": "evt_01HXAB...",
      "type": "lead.created",
      "created": "2026-04-18T08:20:13.482Z",
      "tenant_id": "tnt_2f1...",
      "data": { /* event-specific body */ },
      "api_version": "2026-01-01"
    }

    The data field shape matches the object as returned by the REST API — so a lead.created payload's data is a full Lead object identical to GET /v1/leads/:id.

    Signature Verification

    Every webhook request includes two headers:

    Anvil-Signature: t=1713420013,v1=5257a869e7ecf9d...
    Anvil-Event-Id: evt_01HXAB...

    Verify the signature before trusting the payload. In Node.js:

    import crypto from 'crypto';
    
    function verify(rawBody, header, secret) {
      const [tsPart, sigPart] = header.split(',');
      const timestamp = tsPart.split('=')[1];
      const signature = sigPart.split('=')[1];
      const expected = crypto
        .createHmac('sha256', secret)
        .update(`${timestamp}.${rawBody}`)
        .digest('hex');
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expected)
      );
    }

    Reject any request whose timestamp is more than 5 minutes old to block replay attacks.

    Retries and Delivery Guarantees

    We deliver at-least-once. If your endpoint returns a 2xx within 10 seconds, delivery succeeds. Otherwise we retry with exponential backoff for up to 72 hours (13 attempts). After that the event is dropped and logged in **Settings > Webhooks > Delivery Log**.

    Your endpoint must be idempotent. Deduplicate using Anvil-Event-Id — a single event will carry the same ID across all retry attempts.

    Testing with ngrok

    During development, expose your local server with ngrok:

    ngrok http 3000

    Then register the resulting https://xxx.ngrok.io/webhooks/anvil as your endpoint. Use **Settings > Webhooks > Send Test Event** to fire a synthetic payload without waiting for a real trigger.

    Common Patterns

  • Slack alerts: Subscribe to `lead.created` with a score filter, then post to a Slack incoming-webhook.
  • CRM sync: Subscribe to `lead.created` and `lead.stage_changed`, map fields, call your CRM API.
  • Data warehouse: Subscribe to `export.ready` and download the file URL into your ETL job.