Webhook events
The events you choose reach the HTTPS address you set as a signed POST request.
Overview
Members with the owner or manager role add endpoints in the dashboard and choose which events each one receives.
- 52 events in 8 groups.
- Delivery is at least once: the same event can arrive more than once, so deduplicate on
Speakline-Event-Id. - The address must use
https; redirects are not followed.
Request
A delivery counts as successful when the endpoint answers 2xx within 5 seconds.
| Header | Content |
|---|---|
Speakline-Signature | Timestamp and signature: t=…,v1=… |
Speakline-Event-Type | The event name |
Speakline-Event-Id | The event id (UUID) |
Speakline-Delivery-Id | The delivery id; it stays the same across retries |
POST /speakline-webhook HTTP/1.1
Content-Type: application/json
User-Agent: Speakline-Webhooks/1.0
Speakline-Event-Type: lead.created
Speakline-Event-Id: 0f6a2c4e-8b1d-4e3f-9a5c-7d2e1b0f4c6a
Speakline-Delivery-Id: 3c9d2b7e-41f0-4a8e-b6d5-9e2c7a1f0b84
Speakline-Signature: t=1793572520,v1=5f2c…Event envelope
The body is a JSON envelope with the same fields for every event; event-specific fields sit in payload.
| Field | Content |
|---|---|
event_id | The event id |
event_type | The event name |
schema_version | Envelope version (currently 1) |
company_id | The workspace id |
occurred_at | When the event happened (ISO 8601) |
context | Ids of the related records: call_id, lead_id, appointment_id … |
payload | Fields that depend on the event |
{
"event_id": "0f6a2c4e-8b1d-4e3f-9a5c-7d2e1b0f4c6a",
"event_type": "lead.created",
"schema_version": 1,
"stream": "leads",
"company_id": "7a3e5c1b-9d2f-4b8a-a6e4-c0f2d8b6a4e2",
"occurred_at": "2026-10-23T14:02:11.482000+00:00",
"context": { "lead_id": "5e1d9a3c-7b2f-4c8e-9a61-3f0b2d4c6e81" },
"payload": {
"id": "5e1d9a3c-7b2f-4c8e-9a61-3f0b2d4c6e81",
"full_name": "Emma Clarke",
"phone": "+442079460344",
"email": "emma@example.com",
"source": "inbound_voice",
"status": "new",
"language": "en",
"created_at": "2026-10-23T14:02:11.482000+00:00"
}
}Verifying signatures
The Speakline-Signature header carries t (a Unix timestamp) and v1 (the signature). The signature is HMAC-SHA256 over t, a dot and the raw request body, keyed with the endpoint's secret and written as hex. Every attempt is signed again with a new timestamp.
- Verify against the raw body, before parsing the JSON.
- Compare in constant time.
- We recommend rejecting requests older than 300 seconds.
- The secret starts with
whsec_and is shown once, when it is created or rotated; rotating it invalidates the old one at once.
import crypto from "node:crypto";
// rawBody: the request body exactly as received (string or Buffer), before JSON.parse.
export function verifySpeaklineSignature(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
const timestamp = Number(parts.t);
if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest();
const received = Buffer.from(parts.v1 ?? "", "hex");
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}Retries
Non-2xx responses, timeouts and connection errors count as failures, and the delivery is retried at the intervals below.
If all six attempts fail, the delivery is marked exhausted. The endpoint stays on, and the workspace's owners and managers are notified (at most once a day per endpoint). You can resend any delivery from its history in the dashboard.
| Attempt | After the previous attempt |
|---|---|
| 1 | Immediately |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 6 hours |
Event filter
Each endpoint selects its events with glob patterns. The default * means every event; an empty list sends none. Every pattern must match at least one event.
| Pattern | Events sent |
|---|---|
* | Every event |
lead.* | Lead events |
callcenter.call_* | Call events in the call center |
appointment.booked | New appointments only |
Event catalogue
Call center
| Event | Description |
|---|---|
callcenter.call_queued | Call queued · A caller started waiting in a queue. |
callcenter.call_offered | Ringing an operator · A waiting call rang an operator. |
callcenter.call_answered | Operator answered · An operator answered the call, with the time the caller waited. |
callcenter.call_abandoned | Missed call · The caller hung up while waiting in the queue. |
callcenter.offer_declined | Operator declined · The operator declined the ringing call. |
callcenter.offer_missed | Operator missed · The ringing call went unanswered at the operator. |
callcenter.call_overflowed | Maximum wait reached · The queue's maximum wait ran out and the call went to the overflow target. |
callcenter.callback_requested | Callback requested · The waiting caller asked to be called back instead of holding. |
callcenter.callback_closed | Callback closed · A callback was completed, missed or cancelled. |
callcenter.call_held | Caller put on hold · The operator put the caller on hold. |
callcenter.call_resumed | Back from hold · The caller came back from hold. |
callcenter.call_parked | Call parked · The operator parked the call. |
callcenter.call_picked_up | Picked up from park · An operator picked a parked call up. |
callcenter.call_transferred | Call transferred · The operator transferred the call to a person, a queue or a number. |
callcenter.call_consulted | Consultation · The operator consulted someone during the call. |
callcenter.call_taken_over | Assistant call taken over · A person took over a call the AI assistant was handling. |
callcenter.call_completed | Call completed · A call an operator handled ended, with talk and hold time. |
callcenter.wrap_up_saved | After-call form saved · The operator saved the result, note and tags. |
callcenter.wrap_up_ended | After-call work ended · The operator's after-call work ended. |
callcenter.call_dialed | Operator called out · An operator placed a call (click-to-call, callback, campaign). |
callcenter.extension_dialed | Extension dialed · A caller dialed an operator's extension in a call flow. |
callcenter.internal_call | Operator-to-operator call · An operator called a colleague. |
callcenter.lead_skipped | Campaign card skipped · An operator skipped a lead in an operator campaign. |
callcenter.operator_status_changed | Operator status changed · An operator became available, went on a break or went offline. |
Calls
| Event | Description |
|---|---|
call.originated | Outbound call started · An outbound call was placed by the API, a campaign or an operator. |
call.inbound_received | Inbound call received · An inbound call reached a number. |
call.status_changed | Call status changed · A call moved to a new status (ringing, in progress, completed, failed …). |
call.transferred | Call transferred to a number · The call was transferred to an outside number and answered (assistant, forward rule, call flow, queue overflow or operator). |
call.insights_ready | Call summary ready · The call's summary, sentiment and action items are ready. |
Voicemails
| Event | Description |
|---|---|
voicemail.new | Voicemail left · A caller left a voicemail. |
voicemail.updated | Voicemail processed · A voicemail got its transcript or was matched to a lead. |
Messaging
| Event | Description |
|---|---|
messaging.inbound_received | Inbound message received · A customer message arrived on a channel. |
messaging.inbound_replied | Assistant replied · The AI assistant answered a customer message. |
messaging.inbound_reply_skipped | Assistant did not reply · The AI assistant did not answer (handed over, outside hours …). |
messaging.outbound_sent | Outbound message sent · A message was sent. |
messaging.outbound_manual_sent | Message sent by hand · A person sent a message from the inbox. |
messaging.outbound_sent_voice_tool | Message sent during a call · The voice assistant sent a message during a call. |
messaging.outbound_failed | Message failed · A message could not be delivered. |
nurture.outbound_sent | Follow-up message sent · A follow-up (nurture) message was sent. |
Leads
| Event | Description |
|---|---|
lead.created | Lead created · A lead was created by hand, the API, a form, a call or a message. CSV imports do not send it. |
lead.status_changed | Lead status changed · A lead's status changed (new → contacted → qualified …); the previous status comes too. |
Campaigns
| Event | Description |
|---|---|
campaign.dispatched | Campaign call placed · A campaign called a lead. |
campaign.text_sent | Campaign message sent · A text campaign sent its message to a lead. |
campaign.fallback_sent | Campaign fallback message · A campaign sent its fallback message after an unanswered call. |
campaign.lead_completed | Campaign lead closed · A lead finished an operator campaign. |
Appointments
| Event | Description |
|---|---|
appointment.booked | Appointment booked · An appointment was booked. |
appointment.rescheduled | Appointment rescheduled · An appointment moved to another time. |
appointment.cancelled | Appointment cancelled · An appointment was cancelled. |
appointment.completed | Appointment completed · An appointment was marked completed. |
appointment.no_show | Appointment no-show · The lead missed an appointment. |
appointment.deleted | Appointment deleted · An appointment was deleted. |
Activity log
| Event | Description |
|---|---|
audit.logged | Activity record written · Every activity-log row (who changed what), for a SIEM. |