> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.agentphone.ai/documentation/reference/code-examples/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.agentphone.ai/_mcp/server. # Code Examples > End-to-end examples for common AgentPhone integrations. Copy-paste examples to get up and running quickly. Each example is self-contained and production-ready. ## Voice Webhook Response Reference When your webhook handles a `voice` channel event, the JSON response controls what the agent says and does. All fields are optional. | Field | Type | Description | | -------- | ------------ | -------------------------------------------------------- | | `text` | string | Text the agent speaks to the caller | | `hangup` | boolean | End the call after speaking | | `action` | `"transfer"` | Cold-transfer the caller to the agent's `transferNumber` | ```json // Normal response { "text": "Your order shipped this morning!" } // End the call { "text": "Goodbye!", "hangup": true } // Transfer to a human (requires transferNumber on the agent) { "text": "Let me connect you with our team.", "action": "transfer" } ``` For streaming responses, return `Content-Type: application/x-ndjson` — each line is a JSON object. Set `"interim": true` on non-final chunks so TTS starts immediately. ## Browser Web Call Start a voice call directly in the browser — no phone number needed. Create a web call via the API, then pass the access token to the [AgentPhone Web SDK](https://www.npmjs.com/package/agentphone-web-sdk). ### 1. Create a web call (server-side) **`create-web-call.js`** ```javascript title="create-web-call.js" const res = await fetch("https://api.agentphone.ai/v1/calls/web", { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ agentId: "AGENT_ID", metadata: { userId: "usr_123" }, // optional }), }); const { accessToken } = await res.json(); // Send accessToken to your frontend ``` ### 2. Install the Web SDK ```bash npm install agentphone-web-sdk ``` ### 3. Connect from the browser (client-side) **`web-call-client.js`** ```javascript title="web-call-client.js" import { AgentPhoneWebClient } from "agentphone-web-sdk"; const webClient = new AgentPhoneWebClient(); // accessToken from your backend (valid for 30 seconds) await webClient.startCall({ accessToken }); webClient.on("call_ended", () => { console.log("Call ended"); }); webClient.on("error", (error) => { console.error("Call error:", error); webClient.stopCall(); }); ``` Web calls use the same webhook flow as phone calls — your `agent.message` and `agent.call_ended` webhooks fire normally. The `direction` field will be `"web"` and `fromNumber`/`toNumber` will be `"web"` instead of E.164 numbers. ## Live Transcript Streaming (SSE) Stream a call's transcript in real time using Server-Sent Events. The stream replays existing turns on connect, then delivers new turns as they happen. Works for both live and completed calls. ### Node.js **`stream-transcript.js`** ```javascript title="stream-transcript.js" const API_KEY = "YOUR_API_KEY"; const CALL_ID = "call_abc123"; const res = await fetch( `https://api.agentphone.ai/v1/calls/${CALL_ID}/transcript/stream`, { headers: { Authorization: `Bearer ${API_KEY}` } } ); const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let eventType = null; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop(); // keep incomplete line in buffer for (const line of lines) { if (line.startsWith("event:")) { eventType = line.slice(6).trim(); } else if (line.startsWith("data:")) { const data = JSON.parse(line.slice(5).trim()); if (eventType === "connected") { console.log(`Streaming call ${data.callId} (${data.status})`); } else if (eventType === "turn") { console.log(`[${data.role}] ${data.content}`); } else if (eventType === "ended") { console.log(`Call ended — ${data.durationSeconds}s`); } } } } ``` ### Python **`stream_transcript.py`** ```python title="stream_transcript.py" import json import requests API_KEY = "YOUR_API_KEY" CALL_ID = "call_abc123" url = f"https://api.agentphone.ai/v1/calls/{CALL_ID}/transcript/stream" headers = {"Authorization": f"Bearer {API_KEY}"} with requests.get(url, headers=headers, stream=True) as resp: resp.raise_for_status() event_type = None for line in resp.iter_lines(decode_unicode=True): if not line: continue if line.startswith("event:"): event_type = line[len("event:"):].strip() elif line.startswith("data:"): data = json.loads(line[len("data:"):].strip()) if event_type == "connected": print(f"Streaming call {data['callId']} ({data['status']})") elif event_type == "turn": print(f"[{data['role']}] {data['content']}") elif event_type == "ended": print(f"Call ended — {data['durationSeconds']}s") break ``` See the [Calls guide](/documentation/guides/calls#stream-transcript-sse) for the full SSE event reference. ## JavaScript / Node.js A complete script that provisions a number, registers a webhook, and queries conversations and calls. **`sms-api.js`** ```javascript title="sms-api.js" const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.agentphone.ai"; const headers = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }; async function createNumber() { const res = await fetch(`${BASE_URL}/v1/numbers`, { method: "POST", headers, body: JSON.stringify({ country: "US" }), }); return res.json(); } async function registerWebhook(url) { const res = await fetch(`${BASE_URL}/v1/webhooks`, { method: "POST", headers, body: JSON.stringify({ url }), }); return res.json(); } async function listConversations() { const res = await fetch(`${BASE_URL}/v1/conversations`, { headers }); return res.json(); } async function listCalls() { const res = await fetch(`${BASE_URL}/v1/calls`, { headers }); return res.json(); } async function getCall(callId) { const res = await fetch(`${BASE_URL}/v1/calls/${callId}`, { headers }); return res.json(); } async function getCallTranscript(callId) { const res = await fetch(`${BASE_URL}/v1/calls/${callId}/transcript`, { headers, }); return res.json(); } async function makeOutboundCall(agentId, toNumber, fromNumberId) { const body = { agentId, toNumber }; // Optional: pick which of the agent's numbers to call from. // If omitted, the agent's first assigned number is used. if (fromNumberId) body.fromNumberId = fromNumberId; const res = await fetch(`${BASE_URL}/v1/calls`, { method: "POST", headers, body: JSON.stringify(body), }); return res.json(); } // --- Usage --- const number = await createNumber(); console.log(`Created: ${number.phoneNumber}`); const webhook = await registerWebhook("https://my-server.com/webhook"); console.log(`Webhook secret: ${webhook.secret}`); const convos = await listConversations(); console.log(`${convos.total} conversations`); const calls = await listCalls(); console.log(`${calls.total} calls`); if (calls.data.length > 0) { const call = await getCall(calls.data[0].id); console.log(`Call transcripts: ${call.transcripts.length}`); const transcript = await getCallTranscript(calls.data[0].id); console.log(`Full transcript: ${transcript.transcript.length} turns`); } ``` ## Express.js Webhook Handler Receives webhooks, verifies HMAC signatures, and routes SMS / voice events. **`webhook.js`** ```javascript title="webhook.js" const express = require("express"); const crypto = require("crypto"); const app = express(); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; app.use("/webhook", express.raw({ type: "application/json" })); function verifyWebhook(payload, signature, timestamp, secret) { if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; const signedString = timestamp + "." + payload; const expected = crypto .createHmac("sha256", secret) .update(signedString) .digest("hex"); return signature === `sha256=${expected}`; } app.post("/webhook", (req, res) => { const signature = req.headers["x-webhook-signature"]; const timestamp = req.headers["x-webhook-timestamp"]; if (!verifyWebhook(req.body, signature, timestamp, WEBHOOK_SECRET)) { return res.status(401).send("Invalid signature"); } const payload = JSON.parse(req.body.toString()); if (payload.event === "agent.message") { const { channel, data } = payload; if (channel === "sms") { console.log(`SMS from ${data.from}: ${data.message}`); processMessage(data).catch(console.error); return res.status(200).send("OK"); } if (channel === "voice") { console.log(`Voice from ${data.from}: ${data.transcript}`); processVoice(data) .then((response) => { res.status(200).json(response); }) .catch(() => { res.status(200).json({ text: "Sorry, I encountered an error." }); }); return; } } if (payload.event === "agent.call_ended") { const { data } = payload; console.log( `Call ended: ${data.callId} (${data.durationSeconds}s, ${data.status})` ); console.log(`Transcript: ${data.transcript.length} turns`); onCallEnded(data).catch(console.error); return res.status(200).send("OK"); } res.status(200).send("OK"); }); async function processMessage(message) { // Your message processing logic (AI agent, database, queue, etc.) } async function processVoice(data) { // Return an object with text (and optionally hangup or action) // To transfer: return { text: "Connecting you now.", action: "transfer" } return { text: "Thanks for calling! How can I help?" }; } async function onCallEnded(data) { // Trigger post-call tasks: send email, create CRM contact, generate summary, etc. // data.transcript contains the full conversation as [{role, content}, ...] // data.durationSeconds, data.summary, data.userSentiment are also available } app.listen(3000, () => console.log("Webhook server running on port 3000")); ``` ## Flask Webhook Handler Python equivalent with HMAC verification and SMS/voice routing. **`webhook.py`** ```python title="webhook.py" from flask import Flask, request, jsonify import hmac import hashlib import os import time app = Flask(__name__) WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET") def verify_webhook(payload_body, signature, timestamp, secret): if abs(time.time() - int(timestamp)) > 300: return False signed_string = f"{timestamp}.".encode() + payload_body expected = hmac.new( secret.encode(), signed_string, hashlib.sha256 ).hexdigest() return hmac.compare_digest(f"sha256={expected}", signature) @app.route("/webhook", methods=["POST"]) def webhook(): signature = request.headers.get("X-Webhook-Signature") timestamp = request.headers.get("X-Webhook-Timestamp") if not verify_webhook(request.data, signature, timestamp, WEBHOOK_SECRET): return jsonify({"error": "Invalid signature"}), 401 payload = request.json if payload.get("event") == "agent.message": channel = payload.get("channel") data = payload.get("data", {}) if channel == "sms": print(f"SMS from {data['from']}: {data['message']}") return jsonify({"status": "ok"}), 200 if channel == "voice": transcript = data.get("transcript", "") response = get_ai_response(transcript) return jsonify(response), 200 if payload.get("event") == "agent.call_ended": data = payload.get("data", {}) print(f"Call ended: {data['callId']} ({data['durationSeconds']}s)") print(f"Transcript: {len(data.get('transcript', []))} turns") on_call_ended(data) return jsonify({"status": "ok"}), 200 return jsonify({"status": "ok"}), 200 def get_ai_response(transcript): # Return a dict with text (and optionally hangup or action) # To transfer: return {"text": "Connecting you now.", "action": "transfer"} return {"text": f"I heard you say: {transcript}. How can I help?"} def on_call_ended(data): # Trigger post-call tasks: send email, create CRM contact, generate summary, etc. # data["transcript"] contains the full conversation as [{"role": ..., "content": ...}, ...] # data["durationSeconds"], data["summary"], data["userSentiment"] are also available pass if __name__ == "__main__": app.run(port=3000) ``` ## Voice Webhook with OpenAI A voice-specific handler that pipes caller transcripts through GPT-4 and returns spoken responses. **`voice_webhook.py`** ```python title="voice_webhook.py" from flask import Flask, request, jsonify from openai import OpenAI import os app = Flask(__name__) client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) @app.route("/webhook", methods=["POST"]) def webhook(): payload = request.json if payload.get("event") == "agent.message": channel = payload.get("channel") data = payload.get("data", {}) if channel == "voice": transcript = data.get("transcript", "") try: response = client.chat.completions.create( model="gpt-4", messages=[ {"role": "system", "content": ( "You are a helpful customer service assistant. " "If the caller asks to speak with a human, respond with TRANSFER_NOW." )}, {"role": "user", "content": transcript}, ], max_tokens=150, ) ai_text = response.choices[0].message.content if "TRANSFER_NOW" in ai_text: return jsonify({"text": "Let me connect you with our team.", "action": "transfer"}), 200 return jsonify({"text": ai_text}), 200 except Exception as e: print(f"AI error: {e}") return jsonify({"text": "Sorry, I encountered an error."}), 200 if channel == "sms": print(f"SMS from {data['from']}: {data['message']}") return jsonify({"status": "ok"}), 200 return jsonify({"status": "ok"}), 200 if __name__ == "__main__": app.run(port=3000) ``` ## Next.js API Route Webhook handler as a Next.js Pages Router API route. **`pages/api/webhook.js`** ```javascript title="pages/api/webhook.js" import crypto from "crypto"; const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; function verifyWebhook(payload, signature, timestamp, secret) { if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; const signedString = timestamp + "." + payload; const expected = crypto .createHmac("sha256", secret) .update(signedString) .digest("hex"); return signature === `sha256=${expected}`; } export default async function handler(req, res) { if (req.method !== "POST") { return res.status(405).json({ error: "Method not allowed" }); } const signature = req.headers["x-webhook-signature"]; const timestamp = req.headers["x-webhook-timestamp"]; const rawBody = JSON.stringify(req.body); if (!verifyWebhook(rawBody, signature, timestamp, WEBHOOK_SECRET)) { return res.status(401).json({ error: "Invalid signature" }); } const payload = req.body; if (payload.event === "agent.message") { const { channel, data } = payload; if (channel === "sms") { await processMessage(data); } if (channel === "voice") { const response = await processVoice(data); return res.status(200).json(response); } } if (payload.event === "agent.call_ended") { const { data } = payload; console.log(`Call ended: ${data.callId} (${data.durationSeconds}s)`); await onCallEnded(data); return res.status(200).json({ status: "ok" }); } res.status(200).json({ status: "ok" }); } async function processMessage(message) { console.log(`Processing message: ${message.body}`); } async function processVoice(data) { // Return an object with text (and optionally hangup or action) // To transfer: return { text: "Connecting you now.", action: "transfer" } return { text: "Thanks for calling! How can I help?" }; } async function onCallEnded(data) { // data.transcript: full conversation [{role, content}, ...] // data.durationSeconds, data.summary, data.userSentiment also available } ``` ## Python API Client A standalone script that provisions a number, registers a webhook, and lists conversations. **`sms_api.py`** ```python title="sms_api.py" import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.agentphone.ai" headers = {"Authorization": f"Bearer {API_KEY}"} # Create a phone number number = requests.post( f"{BASE_URL}/v1/numbers", headers={**headers, "Content-Type": "application/json"}, json={"country": "US"}, ).json() print(f"Created: {number['phoneNumber']}") # Register webhook webhook = requests.post( f"{BASE_URL}/v1/webhooks", headers={**headers, "Content-Type": "application/json"}, json={"url": "https://my-server.com/webhook"}, ).json() print(f"Webhook secret: {webhook['secret']}") # List conversations convos = requests.get(f"{BASE_URL}/v1/conversations", headers=headers).json() print(f"{convos['total']} conversations") # Get a specific conversation if convos["data"]: conv = requests.get( f"{BASE_URL}/v1/conversations/{convos['data'][0]['id']}", headers=headers, ).json() print(f"Conversation with {conv['participant']}: {conv['messageCount']} messages") # Make an outbound call (optionally pick which number to call from) call = requests.post( f"{BASE_URL}/v1/calls", headers={**headers, "Content-Type": "application/json"}, json={ "agentId": "AGENT_ID", "toNumber": "+14155551234", # "fromNumberId": "NUMBER_ID", # optional — defaults to agent's first number }, ).json() print(f"Call {call['id']}: {call['fromNumber']} -> {call['toNumber']}") # List calls calls = requests.get(f"{BASE_URL}/v1/calls", headers=headers).json() print(f"{calls['total']} calls") # Fetch full transcript for a completed call if calls["data"]: transcript = requests.get( f"{BASE_URL}/v1/calls/{calls['data'][0]['id']}/transcript", headers=headers, ).json() for turn in transcript["transcript"]: print(f" [{turn['role']}] {turn['content']}") ``` > End-to-end examples for common AgentPhone integrations.