Skip to main content

Documentation Index

Fetch the complete documentation index at: https://patter-06b046ce-feat-observability-otel-attrs-0-6-1.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Local Mode

Local mode runs an embedded Express server on your infrastructure. It handles telephony webhooks, manages WebSocket audio streams, and connects to AI providers directly — no external service required.

Starting the Server

// npx tsx example.ts
import { Patter, Twilio, OpenAIRealtime } from "getpatter";

const phone = new Patter({
  carrier: new Twilio(),                              // TWILIO_* from env
  phoneNumber: "+15550001234",
  webhookUrl: "abc.ngrok.io",
});

const agent = phone.agent({
  engine: new OpenAIRealtime(),                       // OPENAI_API_KEY from env
  systemPrompt: "You are a helpful assistant.",
});

await phone.serve({ agent, port: 8000 });

ServeOptions

ParameterTypeRequiredDefaultDescription
agentAgentYesAgent configuration from phone.agent(...).
portnumberNo8000Port for the embedded server (1-65535).
tunnelbooleanNofalseWhen true, start a cloudflared tunnel automatically (requires cloudflared npm package). Cannot be used together with webhookUrl.
onCallStart(data) => Promise<void>NoCalled when a call connects.
onCallEnd(data) => Promise<void>NoCalled when a call disconnects.
onTranscript(data) => Promise<void>NoCalled for each transcript segment.
onMessage(data) => Promise<string>NoPipeline mode only. Receives user transcript, returns response text. Can also be a URL string for remote webhook/WebSocket integration.
onMetrics(data) => Promise<void>NoCalled after each conversational turn with per-turn latency and cost metrics.
recordingbooleanNofalseEnable call recording (Twilio only).
voicemailMessagestringNoDefault voicemail message for AMD.
pricingRecord<string, Record<string, unknown>>NoCustom pricing overrides for cost calculation.
dashboardbooleanNotrueWhen true, serve a dashboard UI at /dashboard.
dashboardTokenstringNoBearer token for dashboard/API authentication.
manageWebhookbooleanNotrueWhen true, serve() updates the carrier’s webhook URL (Twilio voice_url) on startup. Set to false if the webhook is managed externally (Terraform, an edge gateway, or a router function in front of the agent) — otherwise every boot will silently overwrite the externally-managed value. Ignored when tunnel: true, because the tunnel hostname is dynamic and only known at runtime.

Webhook Endpoints

The embedded server exposes these HTTP endpoints:

Health Check

GET /health
Returns { "status": "ok", "mode": "local" }.

Twilio Endpoints

EndpointMethodPurpose
/webhooks/twilio/voicePOSTHandles incoming calls. Returns TwiML to connect a media stream.
/webhooks/twilio/recordingPOSTReceives recording status callbacks.
/webhooks/twilio/amdPOSTReceives AMD (answering machine detection) results.
/webhooks/twilio/statusPOSTReceives call status callbacks for outbound calls.
Telnyx endpoints are fully supported with feature parity to Twilio (DTMF, transfer, recording).

Telnyx Endpoints

EndpointMethodPurpose
/webhooks/telnyx/voicePOSTHandles incoming calls. Returns commands to answer and start streaming.

WebSocket Streams

Audio streams are handled over WebSocket:
wss://{webhookUrl}/ws/stream/{callId}?caller={caller}&callee={callee}
The server upgrades HTTP connections to WebSocket on the /ws/stream/ path. Each call gets its own WebSocket connection.

Rate Limiting

WebSocket connections are rate-limited to 10 concurrent connections per IP address. Connections exceeding the limit receive a 429 Too Many Requests response. This protects against DoS attacks while being generous enough for legitimate telephony provider traffic (which only opens 1 connection per call).

Security

Twilio Signature Validation

When a Twilio Auth Token is configured (either passed to new Twilio({ authToken }) or via TWILIO_AUTH_TOKEN), all Twilio webhook requests are validated using HMAC-SHA1 signature verification:
  1. The SDK reconstructs the URL from the webhook hostname and request path
  2. Parameters are sorted and concatenated
  3. An HMAC-SHA1 digest is computed using the Twilio Auth Token
  4. The result is compared with the X-Twilio-Signature header using timing-safe comparison
Requests with invalid signatures are rejected with HTTP 403.

Telnyx Signature Validation

When a Telnyx public key is configured (either passed to new Telnyx({ publicKey }) or via TELNYX_PUBLIC_KEY), Telnyx webhook requests are verified using Ed25519 signatures:
  1. The raw request body is captured before JSON parsing
  2. The signed payload is: {timestamp}|{rawBody}
  3. The Ed25519 signature is verified against the Telnyx public key
  4. Requests older than 5 minutes are rejected (replay protection)
Requests with invalid signatures are rejected with HTTP 403.

Architecture

Phone Call


Telephony Provider (Twilio/Telnyx)

    ├── POST /webhooks/{provider}/voice  →  Returns stream instructions


WebSocket /ws/stream/{callId}

    ├── Audio In  → [Transcoding] → AI Provider (OpenAI/ElevenLabs/Pipeline)

    └── Audio Out ← [Transcoding] ← AI Provider


Phone Speaker

Audio Transcoding

ProviderInput FormatTranscoding
Twiliomulaw 8kHzDecoded to PCM 16kHz
TelnyxPCM 16kHzNo transcoding needed
OpenAI TTSPCM 24kHzResampled to 16kHz

Graceful Shutdown

Call phone.disconnect() to gracefully stop the embedded server. The shutdown sequence:
  1. Stop accepting new connections — the HTTP server stops listening.
  2. Hang up active calls — each active call is terminated via the telephony provider API (Twilio or Telnyx).
  3. Close WebSocket connections — a close frame (1001 Server shutting down) is sent to all active WebSocket connections.
  4. Wait for drain — the server waits up to 10 seconds for active connections to close cleanly.
  5. Force-terminate — any connections still open after the drain timeout are forcibly terminated.
  6. Close HTTP server — the underlying HTTP server is fully closed.
// Handle SIGINT/SIGTERM for clean shutdown
process.on("SIGINT", async () => {
  console.log("Shutting down...");
  await phone.disconnect();
  process.exit(0);
});
If a cloudflared tunnel was started with tunnel: true, it is also stopped when disconnect() is called.

Binding Address

The server binds to 0.0.0.0 by default, which means it listens on all network interfaces. This is necessary for the server to accept connections from telephony providers and tunnels, but it also means the server is accessible from other machines on your network.
Because the server binds to 0.0.0.0, it is reachable from any network interface. In production, use a firewall or reverse proxy to restrict access. Always configure the Auth Token / public key so webhook signature verification is enforced.
Use a tunnel (Cloudflare Tunnel, ngrok) to expose the server to the internet for telephony provider webhooks.