Skip to main content

Documentation Index

Fetch the complete documentation index at: https://www.quo.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

The beta webhook API is not a drop-in replacement for the legacy webhook system. Management, signing, and payload shapes all change. This page summarizes the differences and walks through a no-downtime migration.
Quo currently has separate webhook systems for app-managed webhooks and API-managed webhooks. Webhooks created in the Quo app are managed in the app, and webhooks created through the current API are managed through the API. During open beta, beta webhooks are managed separately through the beta webhook endpoints and do not appear in Quo app settings. We plan to unify API and app webhook management.

What changes

AreaLegacyBeta
ManagementApp settings (for app-managed webhooks) and the existing API (for API-managed webhooks).Beta webhooks are created and managed through the beta webhook endpoints. We plan to unify API and app webhook management.
Create endpointsFour near-duplicate create endpoints split by event family (/v1/webhooks/messages, /v1/webhooks/calls, /v1/webhooks/call-summaries, /v1/webhooks/call-transcripts).One POST /webhooks with an events array. Mix message, call, and contact subscriptions in a single webhook.
Event coverageCalls, messages, contacts, transcripts.Messages, call summaries, call transcripts, contacts. Call lifecycle planned later.
Payload shapedata.object with object-specific fields.data.resource + data.context + data.links envelope.
FilteringPer-webhook event subscription, per-phone-number for some event types.resourceIds filter for message and call events; contact events workspace-wide.
Signature headerOpenPhone-Signature.webhook-id + webhook-timestamp + webhook-signature (Standard-Webhooks-compatible).
Signing secretOpenPhone-format secret.whsec_… base64 secret, compatible with the Svix SDK.
VersioningSingle static API version.Date-versioned via x-quo-api-version. Pinned per subscription at creation.
Delivery inspectionNone.Test events, delivery history, per-attempt detail, manual retry.
The signature schemes are not interchangeable. Code that verifies the legacy OpenPhone-Signature header will reject every beta delivery. You must update verification before pointing beta traffic at an existing endpoint — see Validate webhook signatures.

Field remap

The biggest payload change is the split between the primary record and surrounding context.
Legacy fieldBeta field
data.object.iddata.resource.id
data.object.textdata.resource.text
data.object.phoneNumberIddata.context.phoneNumberId
data.object.userIddata.context.userId
data.object.contactIdsdata.context.contacts.ids
data.deepLinkdata.links.quo

Side-by-side: message.received

Before (legacy)
{
  "id": "EV0ea54...",
  "apiVersion": "v4",
  "type": "message.received",
  "data": {
    "object": {
      "id": "AC123",
      "text": "hello",
      "direction": "incoming",
      "phoneNumberId": "PN123",
      "userId": "US123",
      "contactIds": ["CT123"]
    },
    "deepLink": "https://my.quo.com/inbox/..."
  }
}
After (beta)
{
  "id": "EV-message-received",
  "apiVersion": "2026-03-30",
  "type": "message.received",
  "data": {
    "resource": {
      "id": "AC123",
      "text": "hello",
      "direction": "incoming",
      "status": "received",
      "createdAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "senderIdentifier": "+15550001111",
      "recipientIdentifiers": ["+15550002222"]
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
Three structural changes to notice:
  1. Resource identifiers (id, text, status, createdAt) live under resource.
  2. Routing context (phoneNumberId, userId, contacts) lives under context.
  3. Deep links live under links.quo — and are explicitly nullable.

Rollout procedure

The recommended migration runs both webhook systems side by side, compares deliveries, and cuts over once you’ve verified parity. Each step lists what’s running where.
1

Update verification first

Update your endpoint to accept the new webhook-id / webhook-timestamp / webhook-signature headers and a whsec_… secret. See Validate webhook signatures. Your endpoint still verifies legacy OpenPhone-Signature deliveries normally — this step adds a second verifier, doesn’t replace the first.Running: legacy webhooks only.
2

Create the beta subscription, disabled

Create a beta webhook with status: "disabled" and the same target URL. This pins the subscription to 2026-03-30 and gives you a whsec_… secret without firing any traffic yet.Running: legacy webhooks only.
3

Update your handler for beta idempotency

Add idempotency keyed on the webhook-id header for beta delivery retries (see Idempotency). If legacy and beta both send the same business event, use business-field correlation to avoid duplicate side effects across systems.Running: legacy webhooks only.
4

Enable the beta subscription

Flip the beta subscription to status: "enabled". Both systems now deliver events; your handler verifies both signature schemes and dedupes beta retries.Running: legacy + beta in parallel.
5

Compare for parity

Use GET /webhooks/:id/events to inspect beta deliveries and confirm they match what you expected. Common things to verify: data.context.contacts.ids matches your stored contactIds; data.links.quo is non-null where you previously used deepLink; senderIdentifier and recipientIdentifiers look right.Running: legacy + beta in parallel.
6

Disable the legacy webhook

Once parity is verified, disable the legacy webhook in the Quo app or via the legacy API. Your endpoint now serves only beta traffic.Running: beta only.

If something goes wrong

  • Verification failures on every beta delivery. Most likely a body re-serialization issue. See Framework gotchas.
  • Duplicate processing observed. For beta retries, confirm your idempotency store TTL covers the full retry window and that you keyed on the webhook-id header. For duplicate processing across legacy and beta, correlate on stable business fields such as message id, call id, contact id, event type, and event timestamp.
  • Need to roll back. Set the beta subscription status: "disabled". Legacy continues firing because it was never disabled until step 6.

Dual-running both systems

While both systems are active, the same business event may arrive through both systems. Two patterns work:
  • Single endpoint, single handler. Your handler verifies whichever signature header is present. Use webhook-id to dedupe beta retries, and use business-field correlation if you need to suppress duplicate processing across legacy and beta deliveries.
  • Separate endpoints. Point the beta subscription at a distinct URL like /webhooks/quo-beta. This keeps verification logic isolated and makes traffic patterns easy to compare during cutover.
In either case, treat any 2xx response as accepting the delivery. Returning a non-2xx from the wrong handler will trigger retries you don’t want.

See also