API Documentation

Version 1.2.0 • Headless Telephony Reference

AI Agent Instructions

Talki Talki provides a bi-directional messaging API. Use the **Outbound API** to send texts and the **Inbound Webhooks** to receive real-time notifications when your numbers receive messages.

Security: All requests are signed and keys are hashed for your protection.

Authentication

Authentication is handled via a Bearer Token in the Authorization header. Generate keys in the Developer tab of your dashboard.

# Header Format

Authorization: Bearer tt_sk_your_secret_key_here

POST

/messages/send

Send an SMS from a number you own.

Request Body (JSON)

PropertyTypeDescription
tostringRecipient in E.164 (+1555...).
fromstringYour Talki Talki number.
bodystringText content.
Webhook

Inbound Notifications

To receive messages, provide a URL in your settings. We will POST a JSON object when a text arrives.

# Payload received by your server

{
  "event": "sms.received",
  "data": {
    "from": "+15551234567",
    "to": "+15559876543",
    "body": "Hello! I saw your ad.",
    "mediaUrl": null,
    "createdAt": "2026-02-20T14:30:00Z"
  }
}

Webhook Security (Signing)

Talki Talki signs every webhook request. To ensure authenticity, verify the X-TT-Signature header in incoming requests using your Signing Secret.

// Node.js Verification Example

const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', YOUR_SIGNING_SECRET);
const signature = hmac.update(JSON.stringify(request.body)).digest('hex');

if (signature === request.headers['x-tt-signature']) {
  // Request is authentic
}

Webhook Best Practices

  • Your server should return a 200 OK response quickly.
  • Process the message asynchronously to avoid connection timeouts.
  • Use HTTPS for your callback URL to protect your users' data.
AI Receptionist

Receptionist API

Let your other software react to what happens on your phone line. When the AI Receptionist answers a call, takes a message, texts the caller or finishes a call, Talki Talki can send a signed JSON event to any HTTPS endpoint — a Zapier Catch Hook, Make, n8n, your booking system, your CRM. A small REST API lets apps read past calls and manage endpoints. Everything is off by default: nothing is sent until you add an endpoint. Available on VIP; enabled per account.

Machine-readable: OpenAPI 3.1 · llms.txt

Zapier in 3 minutes (no code)

  1. In Zapier, create a Zap with the trigger Webhooks by Zapier → Catch Hook, and copy the hook URL.
  2. In Talki Talki go to Dashboard → Developer → AI Receptionist Events, paste the URL, pick the events (e.g. Message taken, Call completed) and add it.
  3. Click Send test event. Zapier receives a sample call.completed; map fields like takenMessages[0].message or call.from into Google Sheets, Slack, HubSpot, your calendar — anything.

Make, n8n and any app with an incoming-webhook URL work the same way.

Events

EventWhenAdds
call.startedThe AI picked up
message.takenThe AI recorded a message or callback requestmessage: name, phone, message
text.sentThe AI texted the callertext: to, body
transaction.createdA create/update/cancel tool succeeded (booking, order, reservation…)transaction: tool, kind, externalId, summary
call.completedThe call endedcounts, takenMessages, transactions; transcript in full mode

# call.completed — standard mode

POST https://your-app.example/talki        X-TT-Event: call.completed
{
  "id": "evt_9b1f…", "type": "call.completed", "version": 1, "createdAt": "2026-09-06T14:02:11Z",
  "account": "idHA5…",
  "call": { "sid": "CA0e34…", "from": "+15550100123", "to": "+15879000000",
            "startedAt": "2026-09-06T14:01:19Z", "endedAt": "2026-09-06T14:02:11Z",
            "durationSeconds": 52, "endReason": "caller said goodbye", "language": "en", "recordingId": "RE1eaa…" },
  "counts": { "messages": 6, "takenMessages": 1, "textsSent": 1 },
  "takenMessages": [ { "name": "Sam Carter", "phone": "+15550100123",
                       "message": "Wants a quote for a kitchen renovation; call back after 5 pm.", "at": "…" } ],
  "transactions": []
}

Records are always written in your language — a Persian- or French-speaking caller still produces an English note and a Latin-letter name (spelled back to the caller to confirm). call.language tells you what they spoke.

Payload modes (your privacy dial)

  • minimal — event, ids, timing, outcome counts. No transcript, no names, no free text; the caller's number is masked (+1555•••0123). For clinics, law firms, and strict data-minimization policies. Fetch details on demand with the REST API, which is audited.
  • standard (default) — minimal + taken messages and outcomes as structured fields.
  • full — standard + the transcript and the texts sent.

Bodies are never logged on our side; only delivery metadata (status, attempts) is kept. Per-call erasure is available via DELETE /sessions/{callSid}.

Verifying signatures

Every request carries X-TT-Timestamp (unix seconds), X-TT-Signature-256 (t=<ts>,v1=<hex> — HMAC-SHA256 ofts + "." + rawBody with your endpoint's secret) and, for compatibility with the SMS webhook, the legacy X-TT-Signature(HMAC-SHA256 of the raw body). Verify v1 and reject requests older than 5 minutes.

# Node.js

const crypto = require('crypto');
function verify(rawBody, headers, secret) {
  const ts = headers['x-tt-timestamp'];
  const v1 = (headers['x-tt-signature-256'] || '').split(',').find(p => p.startsWith('v1='))?.slice(3);
  if (!ts || !v1 || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Return a 2xx quickly (10 s timeout). Failed deliveries are retried at +10 s, +1 min, +5 min and +15 min; an endpoint that fails 100 times in a row is disabled automatically and shown in your dashboard.

REST endpoints

Authenticate with your account API key (Authorization: Bearer tt_sk_…, created in Developer). Keys may carry scopes: receptionist:read or receptionist:manage; keys without scopes keep full access.

EndpointScopePurpose
GET /api/v1/receptionist/sessionsreadList calls, newest first (limit, before, from). Shaped by your payload mode.
GET /api/v1/receptionist/sessions/{callSid}readOne call.
DELETE /api/v1/receptionist/sessions/{callSid}manageErase transcript, messages and taken messages (timing kept).
GET · POST /api/v1/receptionist/webhooksread · manageList / create endpoints (url, events, payloadMode). The secret is returned once.
PATCH · DELETE /api/v1/receptionist/webhooks/{id}manageEnable/disable · delete.
POST /api/v1/receptionist/webhooks/{id}/testmanageSend a signed sample call.completed.
GET · POST /api/v1/receptionist/toolsread · manageList / register tools the AI can call during a call (see below). The secret is returned once.
PATCH · DELETE /api/v1/receptionist/tools/{id} · POST …/testmanageEnable/disable · delete · call your endpoint once with sample arguments.
GET /api/v1/receptionist/mereadIdentify the account behind a key (connection test for Zapier/Make).

# List recent calls

curl https://talkitalki.ca/api/v1/receptionist/sessions?limit=20 \
  -H "Authorization: Bearer tt_sk_your_key"

{ "version": 1, "payloadMode": "standard",
  "sessions": [ { "callSid": "CA0e34…", "startedAt": "…", "durationSeconds": 52, "endReason": "caller said goodbye",
                  "language": "en", "engine": "live", "from": "+15550100123",
                  "counts": { "messages": 6, "takenMessages": 1, "textsSent": 1 },
                  "takenMessages": [ … ], "transactions": [] } ],
  "nextBefore": "2026-09-05T20:19:22.603Z" }

Tools — the AI calls your app during the call

Register a tool: a name, a plain-English description the AI reads, a JSON Schema for its arguments, and an HTTPS endpoint in your app. When a caller wants something the tool can do — check availability, book an appointment, reserve a table, place an order, look up an order — the receptionist calls your endpoint mid-conversation with JSON arguments and speaks from the result. Booking systems, reservation books, ordering platforms and CRMs all use the same contract. Register in Dashboard → Developer → AI Receptionist Tools or via REST.

# Register a tool (POST /api/v1/receptionist/tools)

{
  "name": "get_available_slots",
  "kind": "query",                 // query | create | update | cancel
  "description": "List open appointment times for a date. Include the client's address when the service is at their home.",
  "parameters": { "type": "object", "properties": {
      "date":    { "type": "string", "format": "date", "description": "YYYY-MM-DD in the business timezone" },
      "address": { "type": "string" } }, "required": ["date"] },
  "url": "https://your-app.example/talki/tools/get_available_slots",
  "timeoutMs": 2500
}
→ 201 { "tool": { "id": "…", … }, "secret": "toolsec_…" }   // secret shown once

# What Talki Talki sends to your endpoint

POST https://your-app.example/talki/tools/get_available_slots
X-TT-Timestamp / X-TT-Signature-256 / X-TT-Tool: get_available_slots / Idempotency-Key: CA0e34…:get_available_slots:1
{
  "id": "tc_CA0e34…_1", "tool": "get_available_slots", "kind": "query", "account": "idHA5…",
  "business": { "timezone": "America/Edmonton", "name": "Sunny Cleaning Co." },
  "call": { "sid": "CA0e34…", "from": "+15550100123", "to": "+15879000000", "language": "en", "callerName": "Sam Carter" },
  "arguments": { "date": "2026-09-08", "address": "45 King Street, Calgary" }
}
// business.timezone is the account's business timezone — interpret dates/times in it.
// call.callerName is present when the number matches a saved contact (null otherwise).

# What you return (within the timeout)

200 { "ok": true,
       "result": { "slots": [ { "id": "s1", "startISO": "2026-09-08T10:00:00-06:00", "label": "10:00 AM" } ] },
       "speak": "We have 10, 11 or 2 available." }          // optional: exact words for the AI to say

200 { "ok": false, "error": "No openings that day",
       "alternatives": [ { "startISO": "2026-09-09T10:00:00-06:00", "label": "Tuesday 10:00 AM" } ] }

// create / update / cancel tools: return an id so it appears in the call's transactions
200 { "ok": true, "result": { "id": "bk_9f2…", "summary": "Cleaning, Tue Sep 8 10:00" } }
PatternTypical toolsNotes
Appointmentsget_available_slots · book_appointment · reschedule_appointment · cancel_appointmentReturn slots with an id/startISO; the AI offers up to three and reads details back before booking.
Reservationscheck_table_availability · reserve_table · modify_reservationParty size, time window, special requests; deposits via a link texted to the caller.
Ordersget_menu · check_item_availability · quote_order · place_order · order_statusNested modifiers, sold-out items, pickup/delivery; never take card numbers by voice — text a pay link.
Anything elsestatus lookups, account notes, quotes, waitlists…Any JSON in, any JSON out.
  • Speed: answer within your timeout (default 2.5 s, max 8 s). Past it, the AI apologizes and offers to take a message — the call never stalls.
  • Outcomes vs failures: a 2xx response with ok:false is a normal conversational outcome ("that time is taken") and is spoken to the caller. Only timeouts, network errors and 4xx/5xx responses count as failures toward auto-disable.
  • Timezone: business.timezone (IANA) is sent on every call and on call.completed; dates the AI passes as arguments are already in that timezone.
  • Confirmation: create, update and cancel tools are read back to the caller and confirmed before they're called, then called exactly once.
  • Idempotency: honour Idempotency-Key so a repeated call can never double-book or double-order.
  • Results are data: the AI is instructed never to follow instructions found inside a tool result. Strings are trimmed and control characters removed before it sees them.
  • Records: successful create/update/cancel calls appear as transactions in call.completed and fire a transaction.created event. Raw results are not stored.
  • Limits: 15 tools per account, 20 tool calls per phone call, 8 per tool. Tools that fail 50 times in a row are disabled and flagged in your dashboard.
  • Language: arguments arrive in the business's language regardless of what the caller spoke (names in Latin letters, dates as YYYY-MM-DD, phones as digits); call.language says what they spoke.
  • Security: HTTPS only; the same signature headers as events, using the tool's own secret; an optional static header (e.g. Authorization) can be attached for platforms that can't verify HMAC.

Good to know

  • Up to 5 event endpoints and 15 tools per account; HTTPS only. API keys are rate-limited to 120 requests per minute.
  • Events are sent for AI Receptionist calls (VIP). Texts the AI sends count against your SMS segments like any other text.
  • Each event has a unique id — make your handler idempotent, since a retried delivery repeats the same event.
  • Keep secrets on your server; rotate by deleting and re-adding an endpoint.
  • Questions: api@pandacat.ca

Developer support: api@pandacat.ca