> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.agentphone.ai/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']}")
```