Integration docs

Webhooks

Point your voice platform at your tenant-scoped endpoints (copy them from Settings — they include your restaurant id):

POST /api/public/webhook/<restaurant_id>/call-started
POST /api/public/webhook/<restaurant_id>/call-ended
POST /api/public/webhook/<restaurant_id>/order-created
POST /api/public/webhook/<restaurant_id>/reservation-created

Authentication

Every request must prove it came from your agent, using your restaurant's signing secret (Settings → Webhook endpoints). Two schemes are accepted:

1. Shared secret header — send the secret verbatim (Vapi's server-secret style):

x-vapi-secret: <your-signing-secret>

2. HMAC signature — sign the raw request body with HMAC-SHA256, hex-encoded:

x-webhook-signature: hex(hmac_sha256(raw_body, <your-signing-secret>))

Requests failing verification are rejected with 401. Unsigned requests are never accepted.

Example: order-created

SECRET="your-signing-secret"
BODY='{
  "call_id": "call_abc123",
  "order_id": "ord_789",
  "customer_name": "Maria",
  "customer_phone": "+14155550123",
  "items": [
    { "name": "Margherita", "quantity": 2,
      "modifiers": [{ "group": "Size", "name": "Large" }] }
  ],
  "notes": "Ring the doorbell"
}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -X POST "https://your-app.example/api/public/webhook/<restaurant_id>/order-created" \
  -H "Content-Type: application/json" \
  -H "x-webhook-signature: $SIG" \
  -d "$BODY"

Items are validated against your live menu and priced server-side; unknown items are kept but flagged for review, so no order is silently lost. Duplicate deliveries (same order_id) are idempotent.

Example: reservation-created

{
  "reservation_id": "res_456",
  "customer_name": "Sam",
  "customer_phone": "+14155559876",
  "party_size": 4,
  "reserved_for": "2026-08-01T19:30:00Z",
  "notes": "Window table if possible"
}

Call events

call-started expects call_id, from_number, to_number. call-ended adds duration_seconds, transcript, language, and optionally a recordingUrl (shown in the dashboard with an audio player). Events arriving out of order are merged by call_id.