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, 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 public REST API — webhooks for "react to events," API for "look up data on demand."

Where to find it

Available events

Seven event types in v1:

EventFires when
call.endedA call completes (answered, then hung up). Payload includes call summary, disposition, contact, optional order block when the call resulted in an order.
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 (warm transfer, scheduled callback, voicemail, ticket). Lets you notify Slack/Teams in real time before the call even ends.
call.appointment_bookedAn appointment was confirmed via a calendar integration. Includes time, duration, service, attendee.
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.

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 Settings → Webhooks in the dashboard
  2. Click Add Webhook
  3. Enter your endpoint URL (must be HTTPS in production)
  4. Select the event types you want to receive
  5. Confirm

You'll see the subscription appear in the list with a secret value (used for signing — see below). Copy it now; you'll only see the full plaintext secret once. After that, only the prefix is shown.

To rotate a secret later (e.g., if it leaks), use the Rotate secret action — 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} (Stripe-style — timestamp + literal period + 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, Python, Ruby, and PHP. 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 Settings → Webhooks → [your subscription] → Deliveries with the timestamp, response status, response body preview, and duration.

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 includes an event_id field in the payload (and as a header). If you process the same event_id twice (e.g., we retried after your endpoint returned 200 but our network swallowed the response), 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 Settings → 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 Allison 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 for the last 30 days with full request/response detail. Use it to verify your endpoint is receiving deliveries and returning 2xx.

Bridge fire pattern (technical aside)

Webhooks are fire-and-forget from the bridge — the call doesn't block on webhook delivery. The bridge POSTs to an internal dispatch endpoint with a 5-second AbortSignal; the dispatch endpoint queues + signs + sends and returns immediately. Your retry budget runs against the dispatcher, not against the bridge. Net: a slow webhook endpoint never slows down the call itself.

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. (You can manually retry individual failed deliveries from the Deliveries view, but only ones that were attempted within the 6-attempt window.)
  • GraphQL or WebSocket subscriptions. REST POST only.
  • Per-event signing keys. The signing secret is per-subscription. No way to rotate per-event-type or per-recipient.
  • Outbound from custom integrations to your endpoint via webhooks. Custom integrations make HTTP requests when the agent decides to use them; they're a different direction than the webhook subscription pattern.

If any of these matter, ask Allison to file a support ticket so the team can prioritize.

Still have questions? Log in to chat with Allison.

Log In to Chat