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.

This is the beta webhook API, available in open beta. For the existing webhook system, see Legacy webhooks.
This guide takes you from zero to a verified, production-shaped webhook delivery in five minutes. Read Overview for the underlying delivery semantics, payload anatomy, and versioning policy.

Before you start

You’ll need:
  • A Quo Public API key with permission to manage webhooks.
  • An HTTPS endpoint you control. For local development, use a tunnel such as ngrok or cloudflared.
  • A runtime that gives you the raw, unparsed request body. This is required for signature verification — see Validate webhook signatures for framework-specific notes.
You can send a real, signed test event before any code is written using POST /webhooks/:id/events/test. See step 4 below.

1. Create the webhook

Pin the subscription to the current API version with x-quo-api-version. The version is recorded once when the webhook is created and used for every subsequent delivery.
curl https://api.openphone.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": ["message.received"],
    "label": "Quickstart webhook"
  }'
Save the key field from the response — that’s your whsec_… signing secret. Treat it like a credential: store it as an environment variable, never in source.

2. Receive the delivery

Each delivery sets three headers and a JSON body. Verify the signature against the raw bytes of the body, then parse.
HeaderPurpose
webhook-idStable delivery identifier. Use as your idempotency key.
webhook-timestampUnix seconds when Quo signed the request.
webhook-signatureSpace-separated v1,<base64-signature> entries.

3. Verify and handle the event

The Svix SDK accepts Quo’s headers and whsec_… key format unchanged. Reject any delivery whose timestamp is more than five minutes off from your server clock to defeat replay.
import { Webhook } from "svix"
import express from "express"

const app = express()
const secret = process.env.QUO_WEBHOOK_KEY ?? ""

app.post(
  "/webhooks/quo",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const deliveryId = req.header("webhook-id")!
    const headers = {
      "webhook-id": deliveryId,
      "webhook-timestamp": req.header("webhook-timestamp")!,
      "webhook-signature": req.header("webhook-signature")!,
    }

    const wh = new Webhook(secret)
    const event = wh.verify(req.body, headers) as { id: string; type: string }

    if (alreadyProcessed(deliveryId)) return res.status(200).end()
    markProcessed(deliveryId)

    handle(event)
    res.status(200).end()
  }
)
Return 200 to acknowledge. Any non-2xx response triggers a retry.

4. Send a test event

Trigger a real, signed delivery to your endpoint without waiting for a real call or message. The response also includes the sample payload inline so you can confirm what your endpoint received.
curl https://api.openphone.com/webhooks/{id}/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" }'
If verification fails, the most common cause is a framework that parsed the JSON before your handler saw the bytes. See Validate webhook signatures.

5. Inspect deliveries

Every delivery is recorded. Use these endpoints to debug a missing or failing event:
  • GET /webhooks/:id/events — recent deliveries with status.
  • GET /webhooks/:id/events/:eventId — request body, all attempts, response codes.
  • POST /webhooks/:id/events/:eventId/retry — manually retry a failed delivery.

Next steps