Event callbacks — webhooks when work finishes

Use when the user asks about streaming, async delivery, webhooks, or push notifications instead of polling enrichment endpoints.

There is no SSE / streaming response on paid enrich routes — each call returns complete JSON when it finishes. Event callbacks are the supported push surface: your HTTPS endpoint receives POSTs when product events fire.

Setup (humans)

Wizard: /connect?action=event-callbacks (admin 411_… bearer required; usage keys get 403).

Setup (API)

Catalog (free, no auth):

curl -sS https://411data.io/api/v1/event-callbacks/catalog

Create subscription (admin bearer, up to 20 active):

curl -sS -X POST https://411data.io/api/v1/event-callbacks/subscriptions \
  -H 'Authorization: Bearer 411_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "label": "CRM sync",
    "event_types": ["lead.session_completed", "lead.enrichment_applied"],
    "callback_url": "https://your-app.example/hooks/411data",
    "http_method": "POST",
    "auth_mode": "hmac_bearer"
  }'

Save signing_secret from the response once (ech_… prefix). Verify X-411data-Signature: sha256=<hmac> per /developers.

Test delivery:

curl -sS -X POST \
  "https://411data.io/api/v1/event-callbacks/subscriptions/{id}/test" \
  -H 'Authorization: Bearer 411_…' \
  -H 'Content-Type: application/json' \
  -d '{"event_type":"enrich.completed"}'

Event types (v1)

event_type When it fires
lead.session_completed POST /api/v1/enrich/lead-session finishes
lead.enrichment_applied Lead backfill persisted to audit store
enrich.completed Single-record enrich matched
enrich.job_completed POST /api/v1/enrich/jobs finishes (virtual true-up or CDP upto settle)
enrich.no_match Bill-on-match refund (no usable rows)
leads.search_completed POST /api/v1/leads/search returns (catalog route; offline by default)
chat.tool_completed Enrich Agent tool call finishes
chat.turn_completed Enrich Agent turn finishes
chat.session_saved Demo transcript persisted
support.ticket_created Support ticket opened (POST /api/v1/support/tickets)
call.disconnected Dialer logs a disconnected-number disposition (POST /api/v1/dialer/events)
disconnected.phone.reappend Reappend finds a new number after a disconnect (emitted by the reappend worker)

Dialer credit subscriptions

To earn disconnect and reappend virtual-USD credits, subscribe to both:

{
  "event_id": 90114,
  "session_id": "b8b9448975794d0b8bf807feb0bea2de",
  "dialer_contact_id": "CRM-88421",
  "phone_e164": "+15555550100",
  "phone_last10": "5555550100",
  "user_code": "DISC",
  "internal_disposition": "disconnected"
}
{
  "event_id": 90114,
  "session_id": "b8b9448975794d0b8bf807feb0bea2de",
  "dialer_contact_id": "CRM-88421",
  "old_phone": "+15555550100",
  "new_phones": [
    {
      "e164": "+15555550199",
      "label": "owner_cell",
      "contact_name": "Kim Livezey",
      "title": "Owner",
      "roles": ["owner", "officer"]
    }
  ]
}

On disconnected.phone.reappend, attach each new_phones[] entry to the contact (use dialer_contact_id to join onto your CRM row), mark old_phone dead, and put the lead back into a redial status so a rep dials the replacement. session_id is the 32-hex enrichment session token from the URL (https://411data.io/enrich_agent?session=…) — use it to reopen the enrichment graph. session_id and dialer_contact_id are null when not supplied on ingest.

Map your dialer's raw disposition codes with PUT /api/v1/dialer/disposition-map (admin or usage-key bearer). Ingest events at POST /api/v1/dialer/events (batch up to 500).

Typical patterns