Help Center/Integrations

Using Outbound Webhooks

How to subscribe to call events and have Allison Voice POST signed payloads to your URL — push notifications instead of polling. Covers events, signing, retries, auto-disable, and testing.

Webhooks let your system react to things that happen on calls, instead of polling the API every few minutes to ask "anything new?". When a call ends, a message is taken, an appointment is booked, or a new contact is auto-created (when auto-save is on, the default), Allison Voice sends a signed HTTP POST to a URL you provide. Your endpoint reads it, does what it needs to do, returns a 2xx, and you're done.

Pair them with the REST API: webhooks for "react to events," API for "look up data on demand."

Where to find it

Available events

Nine event types in v1:

EventFires when
call.endedA call completes (answered, then hung up). Payload includes call summary, disposition, contact ID, optional order block when the call resulted in an order, and a trail field — a short array of caller-perspective step labels you can render as a breadcrumb (e.g., ["Check Availability", "Book Appointment"]). See the call trail article. Robocalls, dialer hang-ups, and wrong numbers are filtered out of this event (and of call.message_taken and call.callback_requested), so your counts reflect real conversations.
call.recording_readyA call recording finished uploading and is ready to fetch. Only fires for organizations with call recording turned on. The upload finishes independently of the call wrapping up, so this event often arrives before call.ended — write handlers that don't depend on the order. Use it as the trigger to call GET /v1/calls/{id}/recording for a presigned playback URL.
call.message_takenA caller asked you to pass on a message. Includes the message text, caller name, callback number, and (when applicable) the team member the message was for.
call.callback_requestedA caller asked someone to call them back. Includes phone number, preferred time, and reason.
call.escalation_triggeredAn escalation rule fired mid-call (a transfer, or a message taken when a transfer wasn't possible). Lets you notify Slack/Teams in real time before the call even ends.
call.appointment_bookedAn appointment was confirmed via a calendar integration. Includes the time, timezone, and the caller's name, email, and phone when captured — enough to send your own confirmation email or sync your CRM without a follow-up API call.
contact.createdA new caller (one we'd never seen before) just had their first call. Auto-creates the contact in your CRM.
call_order.status_changedAn order's status flipped (new → in_progress → fulfilled, or canceled). Order block + new status + previous status. Useful for syncing order state into a POS or fulfillment system.
meeting.outcome_approvedFor organizations using Allison Forge: a meeting outcome was approved and dispatched. Carries the summary, tickets, decisions, and action items so your own automation can act on them.

Subscribe to as many or as few events per webhook subscription as you want. Most subscribers run one or two subscriptions covering the events that matter to their integration, typically call.ended plus whichever event drives their primary flow (e.g., contact.created for a CRM sync, call.appointment_booked for a calendar mirror).

Creating a subscription

  1. Go to Webhooks in the dashboard sidebar (Connect section)
  2. Click New Subscription
  3. Enter your endpoint URL (must be HTTPS)
  4. Select the event types you want to receive
  5. Confirm

The create panel shows your Signing Secret (used for signing, see below). Copy it now; you'll only see the full plaintext secret once. After that, the subscription list shows only a prefix.

To rotate a secret later (e.g., if it leaks), use the Rotate action on the subscription. It produces a new secret, invalidates the old one. There's no overlap window, so coordinate the swap on your endpoint at the same time.

Signing: verify the request came from Allison Voice

Every webhook delivery includes four headers:

X-Allison-Signature: v1=hex_digest_here
X-Allison-Timestamp: 1714857600
X-Allison-Event-Id: evt_abc123...
X-Allison-Event-Type: call.ended

The signature is HMAC-SHA256 over {timestamp}.{rawBody} (timestamp, a literal period, then the raw body), keyed with your subscription's secret. The same timestamp value lives on the X-Allison-Timestamp header so you can detect replay attacks.

Verify on your endpoint by recomputing the same HMAC and constant-time-comparing. Pseudocode:

timestamp = request.headers["X-Allison-Timestamp"]
provided  = request.headers["X-Allison-Signature"].split("=")[1]
signed_string = timestamp + "." + request_body_raw_bytes
expected = hmac_sha256(secret, signed_string)
if (constant_time_equals(expected, provided)) {
  // Authentic Allison Voice request
} else {
  // Reject with 401
}

The docs.allisonvoice.com/webhooks/signing page has working sample code in Node.js and Python. Copy from there.

Important: verify against the raw request body bytes, not the parsed JSON. Some frameworks reformat JSON during parsing, breaking the signature comparison. If your framework gives you parsed JSON, dig for the raw body buffer.

Replay protection: reject deliveries where X-Allison-Timestamp is more than ~5 minutes old. The signature alone proves authenticity but not freshness. Without a timestamp check, a captured payload could be replayed against your endpoint indefinitely.

If your endpoint doesn't verify the signature, anyone who learns your URL can forge events. Always verify in production.

Retry behavior

If your endpoint doesn't return a 2xx within a reasonable timeout, Allison Voice retries on a fixed schedule:

AttemptFires
1Immediately
2+ 1 minute
3+ 15 minutes
4+ 1 hour
5+ 6 hours
6+ 24 hours

After 6 attempts (~31 hours total), the delivery is marked finally failed and we stop retrying. Each attempt is logged in Webhooks → [your subscription] → Deliveries with the timestamp, attempt number, response status, and response body preview.

What counts as "needs retry":

  • Any 5xx response
  • Any 4xx response except 410 Gone (see auto-disable)
  • Connection timeout, refused, or DNS failure

What counts as "delivered":

  • Any 2xx response (even an empty body: we just need 200/201/204)

Idempotency

Each delivery carries a unique event id, as the id field of the payload and as the X-Allison-Event-Id header. If you process the same id twice (e.g., your endpoint processed it, but the acknowledgement never reached us and we retried), treat the second arrival as a no-op rather than a duplicate insert.

A simple pattern: maintain an in-memory or short-lived DB cache of recently-seen event ids. On arrival, check the cache; if present, return 200 immediately without re-processing.

Auto-disable conditions

Two conditions automatically disable a subscription (no manual action needed on our side):

  • Your endpoint returns 410 Gone. Treated as a permanent "this endpoint is gone, stop calling it" signal. Subscription is auto-disabled immediately on the first 410.
  • High failure rate. If a subscription has had ≥10 attempts in the last 24 hours and the failure rate is ≥90%, it auto-disables. Protects you from a flood of retries when your endpoint is sustainably broken.

Auto-disabled subscriptions show in the dashboard with an explanation. Re-enable from the Webhooks page once your endpoint is fixed. Manual enabled: false is a separate disable (you can pause a subscription anytime without changing endpoint behavior).

Testing

Easiest path for local testing:

  1. Use webhook.site or a similar request-bin to get a temporary URL
  2. Create a subscription pointing at that URL
  3. Place a test call to your number, hang up: call.ended fires
  4. Inspect the request body on webhook.site to see the actual payload shape
  5. Once you've seen real payloads, swap the URL for your real endpoint

For production, the Deliveries view in the dashboard shows every attempt with the response status and body. Use it to verify your endpoint is receiving deliveries and returning 2xx.

Delivery never slows the call

Webhook delivery runs separately from the live call. A slow or failing endpoint never delays your agent or the caller; it only affects your delivery log.

Managing subscriptions via the API

Same operations available via the v1 REST API at /v1/webhook-subscriptions: create, list, get, update, delete, rotate secret, view deliveries. See the API reference for the request/response shapes. Useful when you're managing many subscriptions programmatically (e.g., one webhook per Zap, deployed via terraform).

What's not yet supported

A few capabilities are deliberately deferred:

  • Custom retry schedules. The 6-attempt schedule is fixed across all subscribers. No per-subscription tuning.
  • Filtered event delivery. Today you subscribe to event types as a whole. There's no way to say "only call.ended events for calls longer than 60 seconds" or "only contact.created for callers from a specific area code." Filter on your endpoint instead.
  • Replay old events. If a subscription was paused or auto-disabled, events that fired during the down time aren't queued for later delivery. Once you re-enable, you start fresh from new events. There's also no manual retry button; a delivery that has exhausted its 6 attempts stays failed.

Still have questions? Log in to chat with Allison.

Log In to Chat