This is the beta webhook API, available in open beta. For the existing webhook system, see Legacy webhooks.
Use these endpoints to create, manage, test, and inspect webhooks created with the beta API. Endpoints are grouped into CRUD (create/read/update/delete) and Testing & debugging.
Webhooks created through these endpoints are managed through these endpoints and do not appear in the Quo app webhook settings during the beta.
Every request requires your Public API key and the API version header:
Authorization: YOUR_API_KEY
x-quo-api-version: 2026-03-30
Endpoint index
CRUD:
| Method | Path | Purpose |
|---|
GET | /webhooks | List webhooks for the workspace. |
POST | /webhooks | Create a webhook. |
GET | /webhooks/:id | Get a webhook. |
PATCH | /webhooks/:id | Update a webhook. |
DELETE | /webhooks/:id | Delete a webhook. |
POST | /webhooks/:id/rotate | Rotate the signing secret. |
Testing & debugging:
| Method | Path | Purpose |
|---|
POST | /webhooks/:id/events/test | Send a test event to the webhook URL. |
GET | /webhooks/:id/events | List deliveries. |
GET | /webhooks/:id/events/:eventId | Get delivery detail including all attempts. |
POST | /webhooks/:id/events/:eventId/retry | Retry a delivery. |
Supported event types
| 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. |
message.failed | An outbound message failed to deliver. |
call.ringing | A call started ringing. Fires for incoming and outgoing. |
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 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. |
call.recording.completed | A call recording finished processing. |
call.summary.completed | A call summary finished generating. |
call.transcript.completed | A call transcript finished processing. |
call.voicemail.completed | A voicemail was left and finished processing. |
contact.updated | A contact was created or its fields changed. |
contact.deleted | A contact was deleted. |
For payload shapes, see Webhook event payloads.
List webhooks
curl https://api.quo.com/webhooks \
-H "Authorization: YOUR_API_KEY" \
-H "x-quo-api-version: 2026-03-30"
Returns all webhooks created through the beta webhook endpoints for the workspace. This endpoint is not paginated.
{
"data": [
{
"id": "123",
"orgId": "OR123",
"label": "Production webhook",
"status": "enabled",
"url": "https://example.com/webhooks/quo",
"createdAt": "2026-04-13T12:00:00.000Z",
"updatedAt": "2026-04-13T12:00:00.000Z",
"events": ["call.summary.completed", "contact.updated"],
"resourceIds": ["PNabc123"],
"apiVersion": "2026-03-30"
}
]
}
Get a webhook
curl https://api.quo.com/webhooks/123 \
-H "Authorization: YOUR_API_KEY" \
-H "x-quo-api-version: 2026-03-30"
{
"data": {
"id": "123",
"orgId": "OR123",
"label": "Production webhook",
"status": "enabled",
"url": "https://example.com/webhooks/quo",
"createdAt": "2026-04-13T12:00:00.000Z",
"updatedAt": "2026-04-13T12:00:00.000Z",
"events": ["call.summary.completed", "contact.updated"],
"resourceIds": ["PNabc123"],
"apiVersion": "2026-03-30"
}
}
Create a webhook
curl https://api.quo.com/webhooks \
-X POST \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "x-quo-api-version: 2026-03-30" \
-d '{
"url": "https://example.com/webhooks/quo",
"events": ["call.completed", "message.received", "contact.updated"],
"resourceIds": ["PNabc123"],
"label": "Production webhook",
"status": "enabled"
}'
| Field | Type | Required? | Description |
|---|
url | string | Yes | Public HTTPS endpoint that receives webhook deliveries. |
events | string[] | Yes | One or more supported event types. Message, call, and contact events can be mixed in a single subscription. |
resourceIds | string[] | No | Phone number ids for filtering message and call events, or ["*"] for all. Defaults to ["*"]. Contact events are always workspace-wide. |
label | string | No | Human-readable label. |
status | "enabled" | "disabled" | No | Defaults to enabled. |
The response includes the whsec_… signing secret. Save it as an environment variable — see Validate webhook signatures.
{
"data": {
"id": "123",
"orgId": "OR123",
"label": "Production webhook",
"status": "enabled",
"url": "https://example.com/webhooks/quo",
"key": "whsec_...",
"createdAt": "2026-04-13T12:00:00.000Z",
"updatedAt": "2026-04-13T12:00:00.000Z",
"events": ["call.completed", "message.received", "contact.updated"],
"resourceIds": ["PNabc123"],
"apiVersion": "2026-03-30"
}
}
Update a webhook
curl https://api.quo.com/webhooks/123 \
-X PATCH \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "x-quo-api-version: 2026-03-30" \
-d '{
"events": ["call.summary.completed", "contact.updated"],
"resourceIds": ["PNabc123"],
"label": "Updated webhook"
}'
All fields are optional. Provide only the fields you want to change.
| Field | Type | Description |
|---|
url | string | Replaces the webhook URL. |
events | string[] | Replaces the subscribed event types. |
resourceIds | string[] | null | Replaces the phone number filters for message and call events. Send null, [], or ["*"] to clear filtering. |
label | string | null | Replaces the label. Send null to clear it. |
status | "enabled" | "disabled" | Enables or disables delivery. |
Delete a webhook
curl https://api.quo.com/webhooks/123 \
-X DELETE \
-H "Authorization: YOUR_API_KEY" \
-H "x-quo-api-version: 2026-03-30"
Returns 204 No Content on success.
Rotate the signing secret
curl https://api.quo.com/webhooks/123/rotate \
-X POST \
-H "Authorization: YOUR_API_KEY" \
-H "x-quo-api-version: 2026-03-30"
{
"data": { "key": "whsec_..." }
}
Store the new key and use it for future signature verification. See Validate webhook signatures.
Send a test event
Sends a real, signed delivery to your webhook URL — the same as a production event — and returns the sample payload inline so you can inspect it without waiting for the delivery to land.
curl https://api.quo.com/webhooks/123/events/test \
-X POST \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "x-quo-api-version: 2026-03-30" \
-d '{ "eventType": "message.received" }'
eventType must be one of the supported event types. The response is the sample payload sent to your webhook URL. Delivery is asynchronous; use GET /webhooks/:id/events to find the delivery id and inspect what your endpoint returned.
{
"id": "EV-test-message-received",
"apiVersion": "2026-03-30",
"createdAt": "2026-03-30T18:00:00.000Z",
"type": "message.received",
"data": {
"resource": {
"id": "ACsampleactivity0000000000000000",
"direction": "incoming",
"text": "Hello from Quo! This is a sample message.",
"status": "received",
"createdAt": "2026-03-30T18:00:00.000Z"
},
"context": {
"phoneNumberId": "PNsamplephonenumber000000000000",
"conversationId": "CNsampleconversation000000000000",
"userId": "USsampleus",
"contacts": { "ids": ["CTsampleContact01234"], "lookupStatus": "matched" },
"senderIdentifier": "+15555551234",
"recipientIdentifiers": ["+15555555678"]
},
"links": { "quo": "https://my.quo.com/..." }
}
}
List deliveries
curl "https://api.quo.com/webhooks/123/events?limit=10" \
-H "Authorization: YOUR_API_KEY" \
-H "x-quo-api-version: 2026-03-30"
| Parameter | Type | Description |
|---|
limit | number | Page size. |
after | string | Cursor from the previous response’s nextCursor. |
status | "success" | "pending" | "sending" | "failed" | Filter by delivery status. |
eventTypes | string[] | Restrict results to specific event types. |
createdBefore | ISO-8601 string | Only include deliveries created before this time. |
createdAfter | ISO-8601 string | Only include deliveries created after this time. |
Delivery statuses:
| Status | Meaning |
|---|
success | At least one delivery attempt returned a 2xx response. |
pending | Delivery is queued and has not attempted yet. |
sending | Delivery is in progress or waiting for a retry attempt. |
failed | All retry attempts have been exhausted without a 2xx response. |
{
"data": [
{
"id": "msg_2abcDEFghiJKLmnoPQRstu",
"eventType": "message.received",
"status": "success",
"nextAttemptAt": null,
"createdAt": "2026-04-13T12:00:00.000Z"
}
],
"nextCursor": "eyJsYXN0SWQiOiJtc2dfMmFiY0RFRmdoaUpLTG1ub1BRUnN0dSJ9"
}
Get delivery detail
curl https://api.quo.com/webhooks/123/events/msg_2abcDEFghiJKLmnoPQRstu \
-H "Authorization: YOUR_API_KEY" \
-H "x-quo-api-version: 2026-03-30"
Delivery detail includes the request body and all attempts, ordered most-recent first.
{
"data": {
"id": "msg_2abcDEFghiJKLmnoPQRstu",
"eventType": "message.received",
"createdAt": "2026-04-13T12:00:00.000Z",
"requestBody": {
"id": "EV123",
"apiVersion": "2026-03-30",
"createdAt": "2026-04-13T12:00:00.000Z",
"type": "message.received",
"data": {
"resource": {
"id": "AC-message",
"direction": "incoming",
"text": "hello",
"status": "received",
"createdAt": "2026-04-13T12:00:00.000Z"
},
"context": {
"phoneNumberId": "PN123",
"conversationId": "CN123",
"userId": "US123",
"contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
"senderIdentifier": "+15550001111",
"recipientIdentifiers": ["+15550002222"]
},
"links": { "quo": "https://my.quo.com/inbox/..." }
}
},
"attempts": [
{
"id": "atmpt_2abcDEFghiJKLmnoPQRstu",
"timestamp": "2026-04-13T12:00:01.000Z",
"status": "success",
"responseStatusCode": 200,
"responseBody": "{\"ok\":true}",
"responseDurationMs": 123,
"triggerType": "scheduled",
"url": "https://example.com/webhooks/quo"
}
]
}
}
Retry a delivery
curl https://api.quo.com/webhooks/123/events/msg_2abcDEFghiJKLmnoPQRstu/retry \
-X POST \
-H "Authorization: YOUR_API_KEY" \
-H "x-quo-api-version: 2026-03-30"
The retry is queued asynchronously and returns 202 Accepted. Use GET /webhooks/:id/events/:eventId to inspect the new attempt.
See also