Code Examples
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 |
1 // Normal response 2 { "text": "Your order shipped this morning!" } 3 4 // End the call 5 { "text": "Goodbye!", "hangup": true } 6 7 // Transfer to a human (requires transferNumber on the agent) 8 { "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.
1. Create a web call (server-side)
1 const res = await fetch("https://api.agentphone.to/v1/calls/web", { 2 method: "POST", 3 headers: { 4 Authorization: `Bearer ${API_KEY}`, 5 "Content-Type": "application/json", 6 }, 7 body: JSON.stringify({ 8 agentId: "AGENT_ID", 9 metadata: { userId: "usr_123" }, // optional 10 }), 11 }); 12 const { accessToken } = await res.json(); 13 // Send accessToken to your frontend
2. Install the Web SDK
$ npm install agentphone-web-sdk
3. Connect from the browser (client-side)
1 import { AgentPhoneWebClient } from "agentphone-web-sdk"; 2 3 const webClient = new AgentPhoneWebClient(); 4 5 // accessToken from your backend (valid for 30 seconds) 6 await webClient.startCall({ accessToken }); 7 8 webClient.on("call_ended", () => { 9 console.log("Call ended"); 10 }); 11 12 webClient.on("error", (error) => { 13 console.error("Call error:", error); 14 webClient.stopCall(); 15 });
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
1 const API_KEY = "YOUR_API_KEY"; 2 const CALL_ID = "call_abc123"; 3 4 const res = await fetch( 5 `https://api.agentphone.to/v1/calls/${CALL_ID}/transcript/stream`, 6 { headers: { Authorization: `Bearer ${API_KEY}` } } 7 ); 8 9 const reader = res.body.getReader(); 10 const decoder = new TextDecoder(); 11 let buffer = ""; 12 let eventType = null; 13 14 while (true) { 15 const { done, value } = await reader.read(); 16 if (done) break; 17 18 buffer += decoder.decode(value, { stream: true }); 19 const lines = buffer.split("\n"); 20 buffer = lines.pop(); // keep incomplete line in buffer 21 22 for (const line of lines) { 23 if (line.startsWith("event:")) { 24 eventType = line.slice(6).trim(); 25 } else if (line.startsWith("data:")) { 26 const data = JSON.parse(line.slice(5).trim()); 27 28 if (eventType === "connected") { 29 console.log(`Streaming call ${data.callId} (${data.status})`); 30 } else if (eventType === "turn") { 31 console.log(`[${data.role}] ${data.content}`); 32 } else if (eventType === "ended") { 33 console.log(`Call ended — ${data.durationSeconds}s`); 34 } 35 } 36 } 37 }
Python
1 import json 2 import requests 3 4 API_KEY = "YOUR_API_KEY" 5 CALL_ID = "call_abc123" 6 7 url = f"https://api.agentphone.to/v1/calls/{CALL_ID}/transcript/stream" 8 headers = {"Authorization": f"Bearer {API_KEY}"} 9 10 with requests.get(url, headers=headers, stream=True) as resp: 11 resp.raise_for_status() 12 event_type = None 13 for line in resp.iter_lines(decode_unicode=True): 14 if not line: 15 continue 16 if line.startswith("event:"): 17 event_type = line[len("event:"):].strip() 18 elif line.startswith("data:"): 19 data = json.loads(line[len("data:"):].strip()) 20 if event_type == "connected": 21 print(f"Streaming call {data['callId']} ({data['status']})") 22 elif event_type == "turn": 23 print(f"[{data['role']}] {data['content']}") 24 elif event_type == "ended": 25 print(f"Call ended — {data['durationSeconds']}s") 26 break
See the Calls guide for the full SSE event reference.
JavaScript / Node.js
A complete script that provisions a number, registers a webhook, and queries conversations and calls.
1 const API_KEY = "YOUR_API_KEY"; 2 const BASE_URL = "https://api.agentphone.to"; 3 4 const headers = { 5 Authorization: `Bearer ${API_KEY}`, 6 "Content-Type": "application/json", 7 }; 8 9 async function createNumber() { 10 const res = await fetch(`${BASE_URL}/v1/numbers`, { 11 method: "POST", 12 headers, 13 body: JSON.stringify({ country: "US" }), 14 }); 15 return res.json(); 16 } 17 18 async function registerWebhook(url) { 19 const res = await fetch(`${BASE_URL}/v1/webhooks`, { 20 method: "POST", 21 headers, 22 body: JSON.stringify({ url }), 23 }); 24 return res.json(); 25 } 26 27 async function listConversations() { 28 const res = await fetch(`${BASE_URL}/v1/conversations`, { headers }); 29 return res.json(); 30 } 31 32 async function listCalls() { 33 const res = await fetch(`${BASE_URL}/v1/calls`, { headers }); 34 return res.json(); 35 } 36 37 async function getCall(callId) { 38 const res = await fetch(`${BASE_URL}/v1/calls/${callId}`, { headers }); 39 return res.json(); 40 } 41 42 async function getCallTranscript(callId) { 43 const res = await fetch(`${BASE_URL}/v1/calls/${callId}/transcript`, { 44 headers, 45 }); 46 return res.json(); 47 } 48 49 async function makeOutboundCall(agentId, toNumber, fromNumberId) { 50 const body = { agentId, toNumber }; 51 // Optional: pick which of the agent's numbers to call from. 52 // If omitted, the agent's first assigned number is used. 53 if (fromNumberId) body.fromNumberId = fromNumberId; 54 55 const res = await fetch(`${BASE_URL}/v1/calls`, { 56 method: "POST", 57 headers, 58 body: JSON.stringify(body), 59 }); 60 return res.json(); 61 } 62 63 // --- Usage --- 64 const number = await createNumber(); 65 console.log(`Created: ${number.phoneNumber}`); 66 67 const webhook = await registerWebhook("https://my-server.com/webhook"); 68 console.log(`Webhook secret: ${webhook.secret}`); 69 70 const convos = await listConversations(); 71 console.log(`${convos.total} conversations`); 72 73 const calls = await listCalls(); 74 console.log(`${calls.total} calls`); 75 76 if (calls.data.length > 0) { 77 const call = await getCall(calls.data[0].id); 78 console.log(`Call transcripts: ${call.transcripts.length}`); 79 80 const transcript = await getCallTranscript(calls.data[0].id); 81 console.log(`Full transcript: ${transcript.transcript.length} turns`); 82 }
Express.js Webhook Handler
Receives webhooks, verifies HMAC signatures, and routes SMS / voice events.
1 const express = require("express"); 2 const crypto = require("crypto"); 3 const app = express(); 4 5 const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; 6 7 app.use("/webhook", express.raw({ type: "application/json" })); 8 9 function verifyWebhook(payload, signature, timestamp, secret) { 10 if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; 11 const signedString = timestamp + "." + payload; 12 const expected = crypto 13 .createHmac("sha256", secret) 14 .update(signedString) 15 .digest("hex"); 16 return signature === `sha256=${expected}`; 17 } 18 19 app.post("/webhook", (req, res) => { 20 const signature = req.headers["x-webhook-signature"]; 21 const timestamp = req.headers["x-webhook-timestamp"]; 22 23 if (!verifyWebhook(req.body, signature, timestamp, WEBHOOK_SECRET)) { 24 return res.status(401).send("Invalid signature"); 25 } 26 27 const payload = JSON.parse(req.body.toString()); 28 29 if (payload.event === "agent.message") { 30 const { channel, data } = payload; 31 32 if (channel === "sms") { 33 console.log(`SMS from ${data.from}: ${data.message}`); 34 processMessage(data).catch(console.error); 35 return res.status(200).send("OK"); 36 } 37 38 if (channel === "voice") { 39 console.log(`Voice from ${data.from}: ${data.transcript}`); 40 processVoice(data) 41 .then((response) => { 42 res.status(200).json(response); 43 }) 44 .catch(() => { 45 res.status(200).json({ text: "Sorry, I encountered an error." }); 46 }); 47 return; 48 } 49 } 50 51 if (payload.event === "agent.call_ended") { 52 const { data } = payload; 53 console.log( 54 `Call ended: ${data.callId} (${data.durationSeconds}s, ${data.status})` 55 ); 56 console.log(`Transcript: ${data.transcript.length} turns`); 57 onCallEnded(data).catch(console.error); 58 return res.status(200).send("OK"); 59 } 60 61 res.status(200).send("OK"); 62 }); 63 64 async function processMessage(message) { 65 // Your message processing logic (AI agent, database, queue, etc.) 66 } 67 68 async function processVoice(data) { 69 // Return an object with text (and optionally hangup or action) 70 // To transfer: return { text: "Connecting you now.", action: "transfer" } 71 return { text: "Thanks for calling! How can I help?" }; 72 } 73 74 async function onCallEnded(data) { 75 // Trigger post-call tasks: send email, create CRM contact, generate summary, etc. 76 // data.transcript contains the full conversation as [{role, content}, ...] 77 // data.durationSeconds, data.summary, data.userSentiment are also available 78 } 79 80 app.listen(3000, () => console.log("Webhook server running on port 3000"));
Flask Webhook Handler
Python equivalent with HMAC verification and SMS/voice routing.
1 from flask import Flask, request, jsonify 2 import hmac 3 import hashlib 4 import os 5 import time 6 7 app = Flask(__name__) 8 WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET") 9 10 11 def verify_webhook(payload_body, signature, timestamp, secret): 12 if abs(time.time() - int(timestamp)) > 300: 13 return False 14 signed_string = f"{timestamp}.".encode() + payload_body 15 expected = hmac.new( 16 secret.encode(), signed_string, hashlib.sha256 17 ).hexdigest() 18 return hmac.compare_digest(f"sha256={expected}", signature) 19 20 21 @app.route("/webhook", methods=["POST"]) 22 def webhook(): 23 signature = request.headers.get("X-Webhook-Signature") 24 timestamp = request.headers.get("X-Webhook-Timestamp") 25 26 if not verify_webhook(request.data, signature, timestamp, WEBHOOK_SECRET): 27 return jsonify({"error": "Invalid signature"}), 401 28 29 payload = request.json 30 31 if payload.get("event") == "agent.message": 32 channel = payload.get("channel") 33 data = payload.get("data", {}) 34 35 if channel == "sms": 36 print(f"SMS from {data['from']}: {data['message']}") 37 return jsonify({"status": "ok"}), 200 38 39 if channel == "voice": 40 transcript = data.get("transcript", "") 41 response = get_ai_response(transcript) 42 return jsonify(response), 200 43 44 if payload.get("event") == "agent.call_ended": 45 data = payload.get("data", {}) 46 print(f"Call ended: {data['callId']} ({data['durationSeconds']}s)") 47 print(f"Transcript: {len(data.get('transcript', []))} turns") 48 on_call_ended(data) 49 return jsonify({"status": "ok"}), 200 50 51 return jsonify({"status": "ok"}), 200 52 53 54 def get_ai_response(transcript): 55 # Return a dict with text (and optionally hangup or action) 56 # To transfer: return {"text": "Connecting you now.", "action": "transfer"} 57 return {"text": f"I heard you say: {transcript}. How can I help?"} 58 59 60 def on_call_ended(data): 61 # Trigger post-call tasks: send email, create CRM contact, generate summary, etc. 62 # data["transcript"] contains the full conversation as [{"role": ..., "content": ...}, ...] 63 # data["durationSeconds"], data["summary"], data["userSentiment"] are also available 64 pass 65 66 67 if __name__ == "__main__": 68 app.run(port=3000)
Voice Webhook with OpenAI
A voice-specific handler that pipes caller transcripts through GPT-4 and returns spoken responses.
1 from flask import Flask, request, jsonify 2 from openai import OpenAI 3 import os 4 5 app = Flask(__name__) 6 client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) 7 8 9 @app.route("/webhook", methods=["POST"]) 10 def webhook(): 11 payload = request.json 12 13 if payload.get("event") == "agent.message": 14 channel = payload.get("channel") 15 data = payload.get("data", {}) 16 17 if channel == "voice": 18 transcript = data.get("transcript", "") 19 try: 20 response = client.chat.completions.create( 21 model="gpt-4", 22 messages=[ 23 {"role": "system", "content": ( 24 "You are a helpful customer service assistant. " 25 "If the caller asks to speak with a human, respond with TRANSFER_NOW." 26 )}, 27 {"role": "user", "content": transcript}, 28 ], 29 max_tokens=150, 30 ) 31 ai_text = response.choices[0].message.content 32 if "TRANSFER_NOW" in ai_text: 33 return jsonify({"text": "Let me connect you with our team.", "action": "transfer"}), 200 34 return jsonify({"text": ai_text}), 200 35 except Exception as e: 36 print(f"AI error: {e}") 37 return jsonify({"text": "Sorry, I encountered an error."}), 200 38 39 if channel == "sms": 40 print(f"SMS from {data['from']}: {data['message']}") 41 return jsonify({"status": "ok"}), 200 42 43 return jsonify({"status": "ok"}), 200 44 45 46 if __name__ == "__main__": 47 app.run(port=3000)
Next.js API Route
Webhook handler as a Next.js Pages Router API route.
1 import crypto from "crypto"; 2 3 const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; 4 5 function verifyWebhook(payload, signature, timestamp, secret) { 6 if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; 7 const signedString = timestamp + "." + payload; 8 const expected = crypto 9 .createHmac("sha256", secret) 10 .update(signedString) 11 .digest("hex"); 12 return signature === `sha256=${expected}`; 13 } 14 15 export default async function handler(req, res) { 16 if (req.method !== "POST") { 17 return res.status(405).json({ error: "Method not allowed" }); 18 } 19 20 const signature = req.headers["x-webhook-signature"]; 21 const timestamp = req.headers["x-webhook-timestamp"]; 22 const rawBody = JSON.stringify(req.body); 23 24 if (!verifyWebhook(rawBody, signature, timestamp, WEBHOOK_SECRET)) { 25 return res.status(401).json({ error: "Invalid signature" }); 26 } 27 28 const payload = req.body; 29 30 if (payload.event === "agent.message") { 31 const { channel, data } = payload; 32 33 if (channel === "sms") { 34 await processMessage(data); 35 } 36 37 if (channel === "voice") { 38 const response = await processVoice(data); 39 return res.status(200).json(response); 40 } 41 } 42 43 if (payload.event === "agent.call_ended") { 44 const { data } = payload; 45 console.log(`Call ended: ${data.callId} (${data.durationSeconds}s)`); 46 await onCallEnded(data); 47 return res.status(200).json({ status: "ok" }); 48 } 49 50 res.status(200).json({ status: "ok" }); 51 } 52 53 async function processMessage(message) { 54 console.log(`Processing message: ${message.body}`); 55 } 56 57 async function processVoice(data) { 58 // Return an object with text (and optionally hangup or action) 59 // To transfer: return { text: "Connecting you now.", action: "transfer" } 60 return { text: "Thanks for calling! How can I help?" }; 61 } 62 63 async function onCallEnded(data) { 64 // data.transcript: full conversation [{role, content}, ...] 65 // data.durationSeconds, data.summary, data.userSentiment also available 66 }
Python API Client
A standalone script that provisions a number, registers a webhook, and lists conversations.
1 import requests 2 3 API_KEY = "YOUR_API_KEY" 4 BASE_URL = "https://api.agentphone.to" 5 headers = {"Authorization": f"Bearer {API_KEY}"} 6 7 # Create a phone number 8 number = requests.post( 9 f"{BASE_URL}/v1/numbers", 10 headers={**headers, "Content-Type": "application/json"}, 11 json={"country": "US"}, 12 ).json() 13 print(f"Created: {number['phoneNumber']}") 14 15 # Register webhook 16 webhook = requests.post( 17 f"{BASE_URL}/v1/webhooks", 18 headers={**headers, "Content-Type": "application/json"}, 19 json={"url": "https://my-server.com/webhook"}, 20 ).json() 21 print(f"Webhook secret: {webhook['secret']}") 22 23 # List conversations 24 convos = requests.get(f"{BASE_URL}/v1/conversations", headers=headers).json() 25 print(f"{convos['total']} conversations") 26 27 # Get a specific conversation 28 if convos["data"]: 29 conv = requests.get( 30 f"{BASE_URL}/v1/conversations/{convos['data'][0]['id']}", 31 headers=headers, 32 ).json() 33 print(f"Conversation with {conv['participant']}: {conv['messageCount']} messages") 34 35 # Make an outbound call (optionally pick which number to call from) 36 call = requests.post( 37 f"{BASE_URL}/v1/calls", 38 headers={**headers, "Content-Type": "application/json"}, 39 json={ 40 "agentId": "AGENT_ID", 41 "toNumber": "+14155551234", 42 # "fromNumberId": "NUMBER_ID", # optional — defaults to agent's first number 43 }, 44 ).json() 45 print(f"Call {call['id']}: {call['fromNumber']} -> {call['toNumber']}") 46 47 # List calls 48 calls = requests.get(f"{BASE_URL}/v1/calls", headers=headers).json() 49 print(f"{calls['total']} calls") 50 51 # Fetch full transcript for a completed call 52 if calls["data"]: 53 transcript = requests.get( 54 f"{BASE_URL}/v1/calls/{calls['data'][0]['id']}/transcript", 55 headers=headers, 56 ).json() 57 for turn in transcript["transcript"]: 58 print(f" [{turn['role']}] {turn['content']}")

