Spring til indhold
OrderVoice

Integrationsdokumentation

Dit virksomheds-id og din webhook-hemmelighed findes under Indstillinger → Webhook-endpoints.

Log ind for at få dit virksomheds-id og din hemmelighed

Oversigt

Every business has its own webhook secret and its own set of endpoints, identified by the business id in the URL. Copy the exact URLs and the secret from Settings → Webhook endpoints. There are no shared or global endpoints: a request is only ever attributed to the business named in the URL, and it must be signed with that business's secret.

If you sync your agent from Settings we configure the voice platform for you — the sections below are for reference, for custom integrations, and for testing. All examples use https://ordervoice.aarnikainnovations.com; on a self-hosted deployment substitute your own origin.

Autentificering og replay-beskyttelse

Two schemes are accepted on every inbound endpoint. Shared secret: send your webhook secret verbatim in x-vapi-secret (this is what Vapi does). HMAC: sign the raw request body with HMAC-SHA256 keyed by your secret, hex-encoded, in x-webhook-signature, and send the current Unix time in seconds in x-ov-timestamp. The timestamp must be within ±5 minutes of our clock, and a given signature is accepted once — a replay inside the window answers 409.

x-webhook-signature: hex(hmac_sha256(raw_body, <your-webhook-secret>))
x-ov-timestamp: <unix seconds, required with x-webhook-signature>

Unsigned requests are never accepted. An unknown business id, a missing secret and a wrong secret all answer the same 401, so the endpoint cannot be used to discover which ids exist.

Vapi

Set your assistant's server URL to your business endpoint and its server secret to your webhook secret. Vapi sends the secret verbatim in the x-vapi-secret header on every request.

POST https://ordervoice.aarnikainnovations.com/api/webhooks/vapi/<business-id>
x-vapi-secret: <your-webhook-secret>

We handle the status-update, end-of-call-report and tool-calls server messages. The agent's create_order, create_reservation, take_message, lookup_order, cancel_order and transfer tools post through this same URL; we reply with the confirmation text the agent reads back to the caller. Message types we don't use are acknowledged with 200 and { "ok": true, "ignored": true }.

Generiske HMAC-slutpunkter

For any other platform, or your own code, post directly to the five event endpoints:

POST https://ordervoice.aarnikainnovations.com/api/public/webhook/<business-id>/call-started
POST https://ordervoice.aarnikainnovations.com/api/public/webhook/<business-id>/call-ended
POST https://ordervoice.aarnikainnovations.com/api/public/webhook/<business-id>/order-created
POST https://ordervoice.aarnikainnovations.com/api/public/webhook/<business-id>/reservation-created
POST https://ordervoice.aarnikainnovations.com/api/public/webhook/<business-id>/message-taken

Aliases. id is accepted in place of call_id, order_id, reservation_id or message_id; from and to in place of from_number and to_number.

Ping. A signed body of { "type": "ping" } to call-started answers { "ok": true, "pong": true } without creating anything — this is what the "Test webhook" button in Settings sends.

Statuskoder & hastighedsgrænser

200 handled (the JSON body says what happened); 400 the body isn't valid JSON, or a required field is missing; 401 the signature, secret, timestamp or business id doesn't check out; 403 the business is suspended; 404 the URL is malformed (the id isn't a UUID); 409 replayed signature; 413 body over 1 MB; 429 rate limited — back off and retry after the retry-after seconds; 500 something failed on our side (the body is { "ok": false, "error": "internal" }; retry later).

Rate limits. Inbound webhooks: 120 requests burst, refilling 4 per second, per business per source IP; and 600 burst / 20 per second per business across all sources. Public status pages (/order/…, /booking/…): 60 burst, 1 per second per IP. The contact form: 5 per IP, refilling one every 10 minutes.

Eksempel: ordre-oprettet

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

curl -X POST "https://ordervoice.aarnikainnovations.com/api/public/webhook/<business-id>/order-created" \
  -H "Content-Type: application/json" \
  -H "x-webhook-signature: $SIG" \
  -H "x-ov-timestamp: $TS" \
  -d "$BODY"

Items are matched against your live catalog and priced server-side in your currency, including your tax, fee and rounding rules. Every order starts as pending; when an item or modifier doesn't match your catalog the order is kept but starts as needs_review so nothing is silently lost. A status field in the payload is ignored — status is only ever changed from the dashboard. channel is optional: pickup, delivery, dine_in or other; add service_address for delivery.

Duplicate deliveries (the same order_id for the same business) are idempotent: the second delivery is acknowledged with { "ok": true, "duplicate": true } and creates nothing. The same order_id at a different business is a different order.

Eksempel: reservation-oprettet

{
  "reservation_id": "res_456",
  "customer_name": "Sam",
  "customer_phone": "+14155559876",
  "party_size": 4,
  "reserved_for": "<ISO 8601 with offset, e.g. 2026-10-01T19:30:00-07:00>",
  "duration_minutes": 90,
  "resource": "Window table",
  "service_name": "Dinner",
  "notes": "Window table if possible"
}

reserved_for is ISO 8601. Include a timezone offset; if it is missing we interpret the time in your business's timezone. duration_minutes, resource and service_name are optional (the sector default duration applies). Bookings start as confirmed, and duplicates are idempotent per business exactly like orders.

Eksempel: besked-taget

{
  "message_id": "msg_321",
  "customer_name": "Dana",
  "customer_phone": "+14155551234",
  "topic": "Catering quote for 40",
  "message": "Looking for a quote for a Saturday lunch, 40 people.",
  "contact": "Sales"
}

Creates a message in your inbox and emails it to the contact the agent picked (Settings → Agent behavior). contact is matched by label against your configured contacts; unknown labels fall back to the owner. Idempotent per message_id.

Opkaldshændelser

call-started expects call_id, from_number and to_number. call-ended adds duration_seconds, transcript, language, status and optionally a recordingUrl (played back in the dashboard when recording is enabled for the business). Phone numbers are normalised to E.164 using the business's country.

Events may arrive out of order: if call-ended lands before call-started, the two are merged into one call by call_id within your business. Repeated call-started deliveries for the same call_id are idempotent.

Din egen orkestrator (LiveKit, Exotel, Gnani, Bolna)

OrderVoice is the brain; the audio pipeline can be anyone's. A self-hosted LiveKit + Sarvam worker, Exotel AI, an Exotel + Gnani enterprise stack or a Bolna agent all use the same three calls, authorised with the business's webhook secret as a bearer token. Pick the stack under Settings → Phone → Voice stack.

  1. Read the agent spec before each call (it is rebuilt from the live menu, hours and settings on every fetch):
    GET /api/agent/<business_id>/config
    Authorization: Bearer <webhook secret>
    
    { "agent": { "systemPrompt", "greeting", "endCallMessage", "idleMessages", "language", "transferDestinations", … },
      "speech": { "engine": "sarvam", "sarvam": { "speaker": "anushka", "languageCode": "hi-IN" } },
      "tools": [ { "name": "create_order", "description", "parameters", "method": "POST", "url": ".../api/webhooks/tool/<business_id>/create_order" } ],
      "events": { "callStarted": ".../call-started", "callEnded": ".../call-ended" } }
  2. Call tools when the model asks for one. The body is the tool's arguments; the reply carries the handler's JSON plus result, the sentence the agent should read back. Nothing is confirmed to the caller that the backend did not confirm first.
    POST /api/webhooks/tool/<business_id>/create_reservation
    Authorization: Bearer <webhook secret>
    Content-Type: application/json
    
    { "customer_name": "Ravi", "party_size": 4, "reserved_for": "2026-10-12T19:30", "call_id": "abc" }
    
    → { "ok": true, "reservationId": "…", "result": "Booked: table for 4 on Sunday 12 October at 7:30 PM. Reference 3F9A2C." }
  3. Report the call so the dashboard, CRM and analytics see it: POST call-started when it connects and call-ended with the transcript, duration and outcome (see Call events above). Bolna agents do this automatically through their webhook.

Udgående: dit POS, PMS eller CRM

Set an endpoint under Settings → Integrations and every order, booking and message the agent captures is POSTed there as JSON, signed with the same webhook secret in X-Webhook-Signature (sha256 HMAC of the raw body, hex) with the send time in X-Webhook-Timestamp. A Zapier, Make or n8n catch-hook works as-is; a direct endpoint should verify the signature, reject timestamps older than a few minutes, and return 2xx.

POST <your endpoint>
X-Webhook-Event: order.created | reservation.created | reservation.updated | message.created | test.ping
X-Webhook-Signature: <hex hmac-sha256 of body>
X-Webhook-Timestamp: <unix seconds>

{
  "event": "message.created",
  "business_id": "<business-id>",
  "restaurant_id": "<business-id>",
  "sent_at": "<ISO 8601>",
  "data": {
    "message_id": "…", "customer_name": "Dana", "customer_phone": "+14155551234",
    "topic": "Catering quote for 40", "message": "…", "contact": "Sales"
  }
}

business_id is the canonical tenant id; restaurant_id carries the same value and stays for integrations written before the product served more than restaurants. Order payloads include currency and line_total_cents per item; booking payloads include ends_at, duration_minutes and resource. Deliveries show under Settings → Webhook endpoints with provider outbound.

Genforsøg & genafspilning

A delivery that fails (timeout, connection error or a 5xx/429 from your endpoint) is retried automatically on a backoff schedule — immediately, then after 5 s, 30 s, 5 min, 30 min, 2 h, 8 h and 12 h — up to 8 attempts over roughly a day. A 4xx other than 429 is not retried. After the last attempt the delivery is marked failed and stays visible under Settings → Webhook endpoints, where you can replay it with one click once your endpoint is back. Payloads are identical on replay, so keep your handler idempotent on order_id / reservation_id / message_id.

Hvad er nyt

  1. Review release: SMS, transfers, billing, per-sector pages

    • SMS confirmations, order-ready and booking reminders (Twilio, when configured) with a public status link.
    • Warm transfer with a summary, voicemail, after-hours mode and holiday hours.
    • Look-up, reschedule and cancel tools for bookings; guest self-cancel page with add-to-calendar.
    • Per-tenant tax, currency and rounding rules; money shown in your market's currency everywhere.
    • Self-serve billing (Stripe): plan, invoices and overage in Billing.
    • Retention control (retention_days), per-caller erasure, recording toggle and consent disclosure.
    • Two-factor sign-in (TOTP), magic-link sign-in, resend verification, sector chosen at sign-up.
    • Outbound webhooks now carry business_id and an x-ov-timestamp replay-protection header; 3 delivery attempts before a manual replay.
    • New message-taken outbound event; rate limits documented; retry policy documented.
    • Public per-sector landing pages (/for/…), a status page (/status), an honest integrations list and an ROI calculator.
    • Privacy Policy and Terms updated: subprocessors, retention, governing law, DPA, SLA reference.
  2. Knowledge Lab security

    • Per-lab security policy and pgcrypto encryption of document chunk text.
    • Hybrid search fix for document-grounded answers.
  3. Nine sectors, markets and languages

    • Sector registry: restaurants, clinics, salons, real estate, retail, home services, hotels, logistics and Standalone.
    • Market picker with local plan prices; language prefix in every URL.
    • Privacy Policy gained per-market rights sections.

Brug for hjælp?

Every delivery — accepted or rejected — is listed under Settings → Webhook endpoints with its status and error, so you can see exactly what we received. If something still doesn't line up, write to support@aarnikainnovations.com or use the contact form.