This is the beta webhook API, available in open beta. For the existing webhook system, see Legacy webhooks.
id, apiVersion, createdAt, type, and data fields. The sections below document only the per-event data wrapper - the part that varies by event type. The type field on the envelope determines which schema below applies.
Quick index
| Event type | When it fires |
|---|---|
message.received | An inbound SMS, MMS, or message was received by a Quo number. |
message.delivered | An outbound message was delivered. Not a read receipt. |
message.failed | An outbound message failed to deliver. Includes a carrier or provider error code when available. |
call.ringing | A call started ringing. Fires for incoming and outgoing. Context is narrower than other call events. |
call.answered | A call was connected. answeredByUserId identifies the Quo-side user associated with the answer when known. Outgoing calls fire this event for voicemail pickup too. |
call.completed | A call ended. Terminal lifecycle event with the final status and duration. |
call.forwarded | An incoming call was forwarded. Includes the forwarding phone numbers. |
call.missed | An incoming call ended without being answered. Outgoing calls do not fire this event. |
call.recording.completed | A call recording finished processing. May arrive after call.completed. |
call.summary.completed | A call summary finished generating. May arrive long after the call ends. |
call.transcript.completed | A call transcript finished processing. Order vs. summary is not guaranteed. |
call.voicemail.completed | A voicemail was left and finished processing. Use resource.callId to correlate with the source call lifecycle. |
contact.updated | A contact was created or its fields changed. |
contact.deleted | A contact was deleted. Resource shape matches contact.updated. |
Field semantics
Two patterns appear in multiple events. Both indicate why a field may be empty rather than implying “no data exists”.contacts.lookupStatus
Explains why contacts.ids may be empty:
| State | Meaning |
|---|---|
matched | Quo found one or more matching contacts. ids is populated. |
none | Quo checked for matching contacts and found none. ids is []. |
unavailable | Quo could not determine contact matches because the event lacked sufficient context. Treat ids as unknown, not empty. |
participants.resolution
Explains whether participant context was resolved on call events:
| State | Meaning |
|---|---|
available | Workspace and external participants are populated correctly. |
unavailable | Participant context could not be resolved. Treat empty arrays as unknown rather than as no participants. |
Common type aliases
Several events reuse these types. Each event references them by name rather than redefining them inline.message.received
An inbound message was received by Quo. Use this as your inbound trigger.
senderIdentifier and recipientIdentifiers are raw participant identifiers. They are usually E.164 phone numbers, but direct-number and internal flows can emit non-phone identifiers.
message.delivered
An outbound message was delivered. This is delivery confirmation, not a read receipt.
context.conversationId and context.phoneNumberId rather than links.quo.
message.failed
An outbound message failed to deliver. resource.errorCode contains the carrier or provider failure code when one is available; it is null when the failure reason is unknown.
errorCode is the raw code returned by the carrier or messaging provider. It is present only on message.failed and is absent from all other message events.
call.ringing
call.ringing fires once at the start of an incoming or outgoing call. It is intentionally lightweight because call context is not fully available yet.
contacts, participants, and phoneNumberType.
call.answered
A call was connected. answeredByUserId is the Quo user associated with the answer when known. It is not the external party who answered an outgoing call.
call.answered also fires on outgoing calls when the recipient’s voicemail picks up. Treat this event as “the call connected” rather than “a person is on the line.”
call.completed
A call ended. This is the terminal lifecycle event with the final status, duration, and timestamps. Recording, summary, transcript, and voicemail artifacts (if any) arrive as separate events and may be delayed by minutes.
status:
| Value | Meaning |
|---|---|
answered | The call connected. For outgoing calls, this may be either a person answering or the recipient’s voicemail picking up. |
unanswered | The call ended without being answered. May still have a voicemail — check hasVoicemail. |
failed | The call could not be placed or connected. |
forwarded | The call was forwarded to another number. |
abandoned | The caller hung up before the call was answered. |
ai-handled | An AI agent handled the call. |
unknown | Quo could not classify the final state. |
hasVoicemailindicates whether a voicemail was left. The voicemail itself arrives as a separatecall.voicemail.completedevent.durationis in seconds and may benullfor calls that never connected.
call.forwarded
An incoming call was forwarded. This event carries the phone numbers involved in the forward. Other call lifecycle events do not include forwardedFrom or forwardedTo.
call.missed
An incoming call ended without being answered. Outgoing calls do not produce this event — they terminate with call.completed and a non-answered status.
The payload is intentionally minimal. To get the full call shape, look up the call by resource.id in the API.
call.recording.completed
A call recording finished processing. May arrive after call.completed. recordings is always an array — an empty [] means no recording metadata is available in this payload.
call.summary.completed
A call summary finished processing. This is a summary readiness event, not a call-ended event — the call may have ended much earlier. Trust processingStatus, not arrival time, when interpreting summary state.
summaryandnextStepsare arrays whenprocessingStatus === 'completed'; otherwisenull.jobsis an empty array when no AI agent job metadata is available.- Use
callIdto correlate the summary with its source call and any transcript event.
call.transcript.completed
A call transcript finished processing. Transcript and summary events are independent and may arrive in either order for the same call.
identifier without a userId; internal speakers surface as userId with or without an identifier.
call.voicemail.completed
A voicemail was left and finished processing. resource.id is the voicemail activity id. resource.callId is the source call activity id, or null if the source call could not be resolved. Use resource.callId to correlate the voicemail with the rest of the call lifecycle.
transcriptisnullwhile processing or if transcription was not available for the voicemail.- Download or persist the voicemail recording rather than relying on
recordingUrlfor long-term access. - If the source call context cannot be fully resolved,
participants.resolutionmay beunavailableand participant arrays may be empty. If the source call itself cannot be resolved,resource.callIdwill benullandphoneNumberTypewill benull.
contact.updated
A contact was created or its fields changed. Use updatedAt for ordering and freshness checks. Contact events are workspace-wide; see Subscription rules.
customFields[].idis omitted when the source item has no id.- Invalid
numberordatecustom field values normalize tonull. multi-selectvalues are always arrays.
contact.deleted
A contact was deleted. The resource shape matches contact.updated; the event discriminator is type. Soft-deleted email addresses, phone numbers, and custom fields are removed from the payload rather than delivered with a deletion marker.
contact.updated, with type: "contact.deleted" on the envelope.