← Twilio Developer KitCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Twilio Developer Kit
Snapshot Sep 30, 2026 · 22:50 UTC · version 0.2.2
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "twilio-call-recordings",
"description": "Record Twilio voice calls correctly. Covers the critical distinction between Record verb (voicemail) and Dial record (call recording), dual-channel for QA, mid-call pause for PCI, Conference recording, and the ConversationRelay workaround. Use this skill whenever you need to capture call audio for compliance, QA, or analytics.",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 225
}
],
"skill_md_contents": "---\nname: twilio-call-recordings\ndescription: >\n Record Twilio voice calls correctly. Covers the critical distinction\n between Record verb (voicemail) and Dial record (call recording),\n dual-channel for QA, mid-call pause for PCI, Conference recording, and\n the ConversationRelay workaround. Use this skill whenever you need to\n capture call audio for compliance, QA, or analytics.\n---\n\n## Overview\n\nTwilio offers multiple recording methods. Choosing the wrong one is the **#1 developer mistake** in voice — using `<Record>` when you mean `<Dial record>` produces voicemail behavior instead of call recording.\n\n| Method | What it does | Use when |\n|--------|-------------|----------|\n| `<Record>` verb | Records the CALLER only (voicemail-style) | Leaving a message, capturing input |\n| `<Dial record>` | Records BOTH parties on a call | Call recording for two-party calls |\n| `<Start><Recording>` | Starts a recording alongside other verbs | ConversationRelay, multi-verb flows |\n| Conference `record` | Records the conference mix | Multi-party calls |\n| Recordings REST API | Programmatic control mid-call | Pause during payment (PCI) |\n\n---\n\n## Prerequisites\n\n- Twilio account with a voice-capable phone number — see `twilio-account-setup`\n- `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` — see `twilio-iam-auth-setup`\n- SDK: `pip install twilio` / `npm install twilio`\n- A webhook endpoint for recording status callbacks\n- **Compliance check:** Recording consent requirements vary by jurisdiction — see `twilio-compliance-traffic`\n\n---\n\n## Quickstart\n\n### Record a Two-Party Call (Most Common)\n\nUse `<Dial record>` — NOT `<Record>`.\n\n**Python (Flask)**\n```python\nfrom flask import Flask, request\nfrom twilio.twiml.voice_response import VoiceResponse\n\napp = Flask(__name__)\n\n@app.route(\"/voice\", methods=[\"POST\"])\ndef incoming_call():\n response = VoiceResponse()\n response.say(\"This call may be recorded for quality assurance.\")\n dial = response.dial(\n record=\"record-from-answer-dual\", # dual-channel: agent on one, caller on other\n recording_status_callback=\"https://yourapp.com/recording-status\"\n )\n dial.number(\"+15558675310\") # agent's phone\n return str(response)\n```\n\n**Node.js (Express)**\n```node\napp.post(\"/voice\", (req, res) => {\n const response = new VoiceResponse();\n response.say(\"This call may be recorded for quality assurance.\");\n const dial = response.dial({\n record: \"record-from-answer-dual\",\n recordingStatusCallback: \"https://yourapp.com/recording-status\",\n });\n dial.number(\"+15558675310\");\n res.type(\"text/xml\").send(response.toString());\n});\n```\n\n### Handle the Recording Status Callback\n\n> **Security:** Validate `X-Twilio-Signature` on recording callbacks in production. Without validation, attackers could POST fake recording URLs to your endpoint.\n\n**Python (Flask)**\n```python\n@app.route(\"/recording-status\", methods=[\"POST\"])\ndef recording_status():\n recording_sid = request.form[\"RecordingSid\"]\n recording_url = request.form[\"RecordingUrl\"]\n call_sid = request.form[\"CallSid\"]\n status = request.form[\"RecordingStatus\"] # \"completed\", \"failed\"\n duration = request.form.get(\"RecordingDuration\", 0)\n\n if status == \"completed\":\n # Store recording reference\n save_recording(call_sid, recording_sid, recording_url, duration)\n\n return \"\", 200\n```\n\n---\n\n## Key Patterns\n\n### Recording Modes for `<Dial record>`\n\n| Mode | What's recorded | Use case |\n|------|----------------|----------|\n| `record-from-answer` | Single channel, both parties mixed | Simple recording |\n| `record-from-answer-dual` | Dual channel — caller on left, agent on right | QA (separate agent/caller audio) |\n| `record-from-ringing` | Records from ring, not answer | Capture ring time + full call |\n| `record-from-ringing-dual` | Dual channel from ring | QA with ring time |\n\n**Always use `dual` for QA and analytics.** Dual-channel lets speech analytics tools (like Conversation Intelligence) distinguish agent from caller.\n\n### Conference Recording\n\nRecord multi-party calls via the Conference:\n\n**Python**\n```python\nresponse = VoiceResponse()\ndial = response.dial()\ndial.conference(\n \"support-room-123\",\n record=\"record-from-start\", # Records from when conference starts\n recording_status_callback=\"https://yourapp.com/conf-recording-status\"\n)\n```\n\n**Note:** Conference recording captures the main audio mix. Coach/whisper audio is NOT included. See `twilio-conference-calls`.\n\n### ConversationRelay Recording\n\n**Critical:** `record:true` on the REST API call is **silently ignored** with ConversationRelay. No error. No recording.\n\n**Correct approach:** Use `<Start><Recording>` in TwiML before `<Connect>`:\n\n**Python**\n```python\n@app.route(\"/voice\", methods=[\"POST\"])\ndef voice():\n response = VoiceResponse()\n response.say(\"This call may be recorded.\")\n \n # Start recording BEFORE connecting ConversationRelay\n start = Start()\n start.recording(\n recording_status_callback=\"https://yourapp.com/recording-status\",\n recording_status_callback_event=\"completed\"\n )\n response.append(start)\n \n # Now connect ConversationRelay\n connect = Connect()\n connect.conversation_relay(url=\"wss://yourapp.com/ws/voice\")\n response.append(connect)\n \n return str(response)\n```\n\n**Node.js**\n```node\napp.post(\"/voice\", (req, res) => {\n const response = new VoiceResponse();\n response.say(\"This call may be recorded.\");\n \n const start = response.start();\n start.recording({\n recordingStatusCallback: \"https://yourapp.com/recording-status\",\n recordingStatusCallbackEvent: \"completed\",\n });\n \n const connect = response.connect();\n connect.conversationRelay({ url: \"wss://yourapp.com/ws/voice\" });\n \n res.type(\"text/xml\").send(response.toString());\n});\n```\n\n### Mid-Call Pause for PCI Compliance\n\nPause recording when a customer provides payment information:\n\n**Python**\n```python\ndef pause_recording_for_payment(call_sid, recording_sid):\n \"\"\"Pause recording during credit card capture.\"\"\"\n client.calls(call_sid).recordings(recording_sid).update(\n status=\"paused\"\n )\n\ndef resume_recording(call_sid, recording_sid):\n \"\"\"Resume recording after payment processed.\"\"\"\n client.calls(call_sid).recordings(recording_sid).update(\n status=\"in-progress\"\n )\n```\n\n**Node.js**\n```node\nasync function pauseForPayment(callSid, recordingSid) {\n await client.calls(callSid).recordings(recordingSid).update({\n status: \"paused\",\n });\n}\n\nasync function resumeRecording(callSid, recordingSid) {\n await client.calls(callSid).recordings(recordingSid).update({\n status: \"in-progress\",\n });\n}\n```\n\n**PCI DSS:** Never record card numbers. Use Twilio's `<Pay>` verb when possible. If collecting verbally, pause recording for the duration. PCI Mode is IRREVERSIBLE and account-wide — use a sub-account if only some calls need PCI.\n\n### Accessing Recordings\n\n**Python**\n```python\n# List recordings for a specific call\nrecordings = client.recordings.list(call_sid=call_sid)\n\nfor recording in recordings:\n print(f\"SID: {recording.sid}\")\n print(f\"Duration: {recording.duration}s\")\n print(f\"URL: https://api.twilio.com{recording.uri.replace('.json', '.mp3')}\")\n\n# Download a recording\nimport requests as req\naudio = req.get(\n f\"https://api.twilio.com/2010-04-01/Accounts/{account_sid}/Recordings/{recording_sid}.mp3\",\n auth=(account_sid, auth_token)\n)\nwith open(\"recording.mp3\", \"wb\") as f:\n f.write(audio.content)\n\n# Delete a recording (GDPR right to deletion)\nclient.recordings(recording_sid).delete()\n```\n\n### Recording Storage & Retention\n\n| Feature | Default | Notes |\n|---------|---------|-------|\n| Storage location | Twilio cloud | Can configure external storage (S3, GCS) |\n| Retention | Indefinite | Delete manually via API or set auto-delete policy |\n| Formats | WAV (default), MP3 | Request MP3 by appending `.mp3` to URL |\n| Encryption | At rest | Additional encryption with PCI Mode |\n\n---\n\n## Common Errors\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| Recording captures only caller (no agent) | Used `<Record>` verb instead of `<Dial record>` | Switch to `<Dial record=\"record-from-answer\">` |\n| No recording at all | Used REST API `record:true` with ConversationRelay | Use `<Start><Recording>` in TwiML |\n| Recording is empty / silent | Webhook endpoint unreachable, recording never started | Check StatusCallback URL reachability |\n| Recording has both parties on same channel | Used `record-from-answer` (mono) | Use `record-from-answer-dual` for separate channels |\n| Coach audio missing from conference recording | Expected behavior — coach audio isn't in the mix | Record coach's call leg separately |\n\n---\n\n## CANNOT\n\n- **`recordingTrack` has no observable effect via TwiML** — The `<Start><Recording>` TwiML parameter `recordingTrack` does not isolate tracks. Use the Recordings REST API with `recordingTrack` for actual track isolation.\n- **Cannot start API recordings on ConversationRelay calls** — REST API `record:true` is silently ignored (\"not eligible for recording\"). Must use `<Start><Recording>` before `<Connect>` in TwiML.\n- **Cannot pause/resume recordings via TwiML** — Only available via the REST API (`update` with `status=\"paused\"` or `status=\"in-progress\"`).\n- **Cannot get dual-channel conference recordings** — Conference recording is always mono (mixed).\n- **Cannot get dual-channel from Calls API without explicit param** — `Record=true` defaults to mono. Must specify `recordingChannels: 'dual'`.\n- **Cannot transcribe PCI-mode recordings** — Recordings created while PCI mode was enabled cannot be transcribed, even after PCI is disabled.\n- **Cannot use `<Record>` verb for call recording** — `<Record>` captures the caller only (voicemail-style). Use `<Dial record>` or `<Start><Recording>` for call recording.\n- **Cannot capture coach/whisper audio in conference recordings** — Supervisor whisper is excluded from the mix\n- **Cannot reverse PCI Mode** — PCI Mode is irreversible and account-wide. Once enabled, all recordings are encrypted.\n- **Cannot auto-delete recordings without configuration** — Recordings are retained indefinitely unless you configure auto-deletion\n- **Cannot avoid larger file sizes with dual-channel** — Dual-channel recordings are ~2x the size of mono. Factor into storage costs.\n\n---\n\n## Next Steps\n\n- **Conference calls:** `twilio-conference-calls`\n- **Agent routing:** `twilio-taskrouter-routing`\n- **Compliance:** `twilio-compliance-traffic`\n- **Debug recording issues:** `twilio-debugging-observability`\n"
}SHA-256: 7ab9feb7fd6968067baed52b2eb0b166e9ff74f9966f8ea12df2a1bd33a95718