{"slug":"swml-record-and-transcribe","title":"SWML Record and Transcribe — Background Recording, Live Transcription, Tap","tags":["signalwire","swml","recording","transcription","live-transcribe","tap"],"agent_summary":"Record calls with `record_call` (background, stereo) and `record` (foreground, voicemail). Stream real-time transcripts via `live_transcribe`. Fork audio to a third party with `tap`. Includes status webhook contract and Deepgram/Google speech-engine config.","trigger_phrases":["record call SWML","record_call vs record","live_transcribe webhook","Deepgram in SignalWire","stereo recording stereo true","stop_record_call","tap SWML","speaker diarization SignalWire"],"runnable":true,"markdown":"\n# Recording and Transcribing Calls in SWML\n\nSignalWire offers two recording verbs and one transcription verb. Pick the right one based on whether you need to keep the call going while recording, and whether you need real-time transcripts.\n\n| Verb | Mode | Use for |\n|---|---|---|\n| `record_call` | Background, non-blocking | Dashboards, analytics, AI-assisted calls. Runs alongside `ai`. |\n| `record` | Foreground, blocking | Voicemail — call pauses until recording ends. |\n| `live_transcribe` | Background | Real-time transcript streaming to your webhook. |\n| `tap` | Background | Fork audio to a third-party endpoint (monitoring, fraud detection). |\n\n## `record_call` — background dual-channel recording\n\n```yaml\nversion: 1.0.0\nsections:\n  main:\n    - answer: {}\n    - record_call:\n        format: mp3            # \"wav\" | \"mp3\" | \"mp4\"\n        stereo: true           # required for speaker diarization\n        direction: both        # \"speak\" | \"listen\" | \"both\"\n        beep: false\n        input_sensitivity: 44.0\n        initial_timeout: 0\n        end_silence_timeout: 0\n        max_length: 3600\n        status_url: \"https://your.api/webhooks/recording\"\n    - ai:\n        prompt: { text: \"You are a sales analyst...\" }\n```\n\n`record_call` is non-blocking — it runs in the background while subsequent verbs (`ai`, `connect`, etc.) execute on top of it. Always set `stereo: true` if you need to separate speakers later. Without stereo both voices mix into one channel and per-speaker sentiment analysis becomes impossible.\n\n### Variables set\n\n| Variable | Description |\n|---|---|\n| `${record_call_url}` | URL to download the recording |\n| `${record_call_result}` | `success` or `failed` |\n| `${record_control_id}` | Control ID for `stop_record_call` |\n\n### Stopping it mid-call\n\n```yaml\n- stop_record_call: {}\n```\n\n## `record` — foreground voicemail-style recording\n\n`record` blocks the call until the recording ends. Use for voicemail.\n\n```yaml\n- record:\n    stereo: true\n    format: wav\n    direction: speak         # default — caller's voice only\n    beep: true\n    end_silence_timeout: 5.0\n    terminators: \"#\"\n    max_length: 120\n    status_url: \"https://your.api/webhooks/recording\"\n```\n\n### Variables set\n\n| Variable | Description |\n|---|---|\n| `${record_url}` | URL of the foreground recording |\n| `${record_result}` | `success` or `failed` |\n\n## Recording status webhook payload\n\n`status_url` receives this JSON when recording state changes:\n\n```json\n{\n  \"event_type\": \"calling.call.record\",\n  \"timestamp\": 1640000000.123,\n  \"project_id\": \"PROJECT-UUID\",\n  \"params\": {\n    \"state\": \"finished\",\n    \"control_id\": \"CONTROL-UUID\",\n    \"call_id\": \"CALL-UUID\",\n    \"url\": \"https://your-space.signalwire.com/api/v1/recordings/rec-uuid/download\",\n    \"duration\": 125.4,\n    \"size\": 2048000,\n    \"recording_id\": \"REC-UUID\",\n    \"start_time\": 1640000000.0,\n    \"end_time\": 1640000125.4,\n    \"record\": {\n      \"audio\": { \"format\": \"mp3\", \"direction\": \"both\", \"stereo\": true }\n    }\n  }\n}\n```\n\n`params.state` values: `recording`, `paused`, `finished`, `no_input`, `error`.\n\n## `live_transcribe` — real-time transcripts\n\nStreams partial and final ASR results to your webhook as the conversation happens. Backed by Deepgram (default) or Google.\n\n```yaml\nversion: 1.0.0\nsections:\n  main:\n    - answer: {}\n    - live_transcribe:\n        action:\n          start:\n            webhook: \"https://your.api/webhooks/transcription\"\n            lang: en                  # required ISO code\n            live_events: true         # partial results in real time\n            ai_summary: true          # AI summary at session end\n            ai_summary_prompt: \"Summarize key points, intent, and action items.\"\n            direction:\n              - remote-caller         # the inbound caller\n              - local-caller          # your AI or agent\n            speech_engine: deepgram   # \"deepgram\" (default) | \"google\"\n            speech_timeout: 60000     # ms, min 1500\n            vad_silence_ms: 300       # ms silence to end utterance (300 deepgram, 500 google)\n            vad_thresh: 400           # VAD sensitivity 0-1800\n```\n\n### Stop, summarize, or restart mid-call\n\n```yaml\n- live_transcribe: { action: { stop: {} } }\n- live_transcribe: { action: { summarize: {} } }\n```\n\n### Direction values\n\n- `remote-caller` — the inbound caller's audio.\n- `local-caller` — your local party (AI or human agent).\n\nInclude both to transcribe the full conversation.\n\n## `tap` — fork audio to a third party\n\nSends a copy of the audio stream to another endpoint for live monitoring, fraud detection, or compliance.\n\n```yaml\n- tap:\n    uri: \"rtmps://stream.example.com/live/key\"\n    direction: both\n    codec: PCMU\n```\n\nTap can target RTMP, WebSocket, or another SIP endpoint. The original call continues unaffected.\n\n## Concurrent verbs — recording + AI + live transcript\n\nAll three are non-blocking. Stack them.\n\n```yaml\nversion: 1.0.0\nsections:\n  main:\n    - answer: {}\n    - record_call:\n        format: mp3\n        stereo: true\n        status_url: \"https://your.api/webhooks/recording\"\n    - live_transcribe:\n        action:\n          start:\n            webhook: \"https://your.api/webhooks/transcript\"\n            lang: en\n            live_events: true\n            direction: [remote-caller, local-caller]\n    - ai:\n        prompt: { text: \"You are an HVAC dispatcher.\" }\n        post_prompt:\n          text: \"Return JSON with intent, address, scheduled.\"\n        post_prompt_url: \"https://your.api/webhooks/post-prompt\"\n```\n\nThis is the canonical [Call Intelligence pipeline](/topic/signalwire-call-intelligence) shape.\n\n## Anti-patterns\n\n- Using `record` when you also need `ai` — `record` blocks. Use `record_call` instead.\n- `stereo: false` for sentiment dashboards — kills speaker diarization.\n- Missing `status_url` — you get a recording but no callback that tells you it's ready.\n- Setting `live_transcribe` `direction` to only `remote-caller` when you want both sides — you'll only see what the caller said, not what the AI replied.\n- Setting `vad_silence_ms` below 250ms with Deepgram — splits utterances mid-sentence.\n- Setting `max_length` too high (3600+) — you'll have multi-GB recordings to ferry.\n\n## See also\n\n- [SWML AI verb](/topic/swml-ai-verb)\n- [Call Intelligence pipeline](/topic/signalwire-call-intelligence)\n- [AssemblyAI transcription](/topic/assemblyai-transcription)\n","html":"<h1>Recording and Transcribing Calls in SWML</h1>\n<p>SignalWire offers two recording verbs and one transcription verb. Pick the right one based on whether you need to keep the call going while recording, and whether you need real-time transcripts.</p>\n<p>| Verb | Mode | Use for |\n|---|---|---|\n| <code>record_call</code> | Background, non-blocking | Dashboards, analytics, AI-assisted calls. Runs alongside <code>ai</code>. |\n| <code>record</code> | Foreground, blocking | Voicemail — call pauses until recording ends. |\n| <code>live_transcribe</code> | Background | Real-time transcript streaming to your webhook. |\n| <code>tap</code> | Background | Fork audio to a third-party endpoint (monitoring, fraud detection). |</p>\n<h2><code>record_call</code> — background dual-channel recording</h2>\n<pre><code class=\"language-yaml\">version: 1.0.0\nsections:\n  main:\n    - answer: {}\n    - record_call:\n        format: mp3            # \"wav\" | \"mp3\" | \"mp4\"\n        stereo: true           # required for speaker diarization\n        direction: both        # \"speak\" | \"listen\" | \"both\"\n        beep: false\n        input_sensitivity: 44.0\n        initial_timeout: 0\n        end_silence_timeout: 0\n        max_length: 3600\n        status_url: \"https://your.api/webhooks/recording\"\n    - ai:\n        prompt: { text: \"You are a sales analyst...\" }\n</code></pre>\n<p><code>record_call</code> is non-blocking — it runs in the background while subsequent verbs (<code>ai</code>, <code>connect</code>, etc.) execute on top of it. Always set <code>stereo: true</code> if you need to separate speakers later. Without stereo both voices mix into one channel and per-speaker sentiment analysis becomes impossible.</p>\n<h3>Variables set</h3>\n<p>| Variable | Description |\n|---|---|\n| <code>${record_call_url}</code> | URL to download the recording |\n| <code>${record_call_result}</code> | <code>success</code> or <code>failed</code> |\n| <code>${record_control_id}</code> | Control ID for <code>stop_record_call</code> |</p>\n<h3>Stopping it mid-call</h3>\n<pre><code class=\"language-yaml\">- stop_record_call: {}\n</code></pre>\n<h2><code>record</code> — foreground voicemail-style recording</h2>\n<p><code>record</code> blocks the call until the recording ends. Use for voicemail.</p>\n<pre><code class=\"language-yaml\">- record:\n    stereo: true\n    format: wav\n    direction: speak         # default — caller's voice only\n    beep: true\n    end_silence_timeout: 5.0\n    terminators: \"#\"\n    max_length: 120\n    status_url: \"https://your.api/webhooks/recording\"\n</code></pre>\n<h3>Variables set</h3>\n<p>| Variable | Description |\n|---|---|\n| <code>${record_url}</code> | URL of the foreground recording |\n| <code>${record_result}</code> | <code>success</code> or <code>failed</code> |</p>\n<h2>Recording status webhook payload</h2>\n<p><code>status_url</code> receives this JSON when recording state changes:</p>\n<pre><code class=\"language-json\">{\n  \"event_type\": \"calling.call.record\",\n  \"timestamp\": 1640000000.123,\n  \"project_id\": \"PROJECT-UUID\",\n  \"params\": {\n    \"state\": \"finished\",\n    \"control_id\": \"CONTROL-UUID\",\n    \"call_id\": \"CALL-UUID\",\n    \"url\": \"https://your-space.signalwire.com/api/v1/recordings/rec-uuid/download\",\n    \"duration\": 125.4,\n    \"size\": 2048000,\n    \"recording_id\": \"REC-UUID\",\n    \"start_time\": 1640000000.0,\n    \"end_time\": 1640000125.4,\n    \"record\": {\n      \"audio\": { \"format\": \"mp3\", \"direction\": \"both\", \"stereo\": true }\n    }\n  }\n}\n</code></pre>\n<p><code>params.state</code> values: <code>recording</code>, <code>paused</code>, <code>finished</code>, <code>no_input</code>, <code>error</code>.</p>\n<h2><code>live_transcribe</code> — real-time transcripts</h2>\n<p>Streams partial and final ASR results to your webhook as the conversation happens. Backed by Deepgram (default) or Google.</p>\n<pre><code class=\"language-yaml\">version: 1.0.0\nsections:\n  main:\n    - answer: {}\n    - live_transcribe:\n        action:\n          start:\n            webhook: \"https://your.api/webhooks/transcription\"\n            lang: en                  # required ISO code\n            live_events: true         # partial results in real time\n            ai_summary: true          # AI summary at session end\n            ai_summary_prompt: \"Summarize key points, intent, and action items.\"\n            direction:\n              - remote-caller         # the inbound caller\n              - local-caller          # your AI or agent\n            speech_engine: deepgram   # \"deepgram\" (default) | \"google\"\n            speech_timeout: 60000     # ms, min 1500\n            vad_silence_ms: 300       # ms silence to end utterance (300 deepgram, 500 google)\n            vad_thresh: 400           # VAD sensitivity 0-1800\n</code></pre>\n<h3>Stop, summarize, or restart mid-call</h3>\n<pre><code class=\"language-yaml\">- live_transcribe: { action: { stop: {} } }\n- live_transcribe: { action: { summarize: {} } }\n</code></pre>\n<h3>Direction values</h3>\n<ul>\n<li><code>remote-caller</code> — the inbound caller's audio.</li>\n<li><code>local-caller</code> — your local party (AI or human agent).</li>\n</ul>\n<p>Include both to transcribe the full conversation.</p>\n<h2><code>tap</code> — fork audio to a third party</h2>\n<p>Sends a copy of the audio stream to another endpoint for live monitoring, fraud detection, or compliance.</p>\n<pre><code class=\"language-yaml\">- tap:\n    uri: \"rtmps://stream.example.com/live/key\"\n    direction: both\n    codec: PCMU\n</code></pre>\n<p>Tap can target RTMP, WebSocket, or another SIP endpoint. The original call continues unaffected.</p>\n<h2>Concurrent verbs — recording + AI + live transcript</h2>\n<p>All three are non-blocking. Stack them.</p>\n<pre><code class=\"language-yaml\">version: 1.0.0\nsections:\n  main:\n    - answer: {}\n    - record_call:\n        format: mp3\n        stereo: true\n        status_url: \"https://your.api/webhooks/recording\"\n    - live_transcribe:\n        action:\n          start:\n            webhook: \"https://your.api/webhooks/transcript\"\n            lang: en\n            live_events: true\n            direction: [remote-caller, local-caller]\n    - ai:\n        prompt: { text: \"You are an HVAC dispatcher.\" }\n        post_prompt:\n          text: \"Return JSON with intent, address, scheduled.\"\n        post_prompt_url: \"https://your.api/webhooks/post-prompt\"\n</code></pre>\n<p>This is the canonical <a href=\"/topic/signalwire-call-intelligence\">Call Intelligence pipeline</a> shape.</p>\n<h2>Anti-patterns</h2>\n<ul>\n<li>Using <code>record</code> when you also need <code>ai</code> — <code>record</code> blocks. Use <code>record_call</code> instead.</li>\n<li><code>stereo: false</code> for sentiment dashboards — kills speaker diarization.</li>\n<li>Missing <code>status_url</code> — you get a recording but no callback that tells you it's ready.</li>\n<li>Setting <code>live_transcribe</code> <code>direction</code> to only <code>remote-caller</code> when you want both sides — you'll only see what the caller said, not what the AI replied.</li>\n<li>Setting <code>vad_silence_ms</code> below 250ms with Deepgram — splits utterances mid-sentence.</li>\n<li>Setting <code>max_length</code> too high (3600+) — you'll have multi-GB recordings to ferry.</li>\n</ul>\n<h2>See also</h2>\n<ul>\n<li><a href=\"/topic/swml-ai-verb\">SWML AI verb</a></li>\n<li><a href=\"/topic/signalwire-call-intelligence\">Call Intelligence pipeline</a></li>\n<li><a href=\"/topic/assemblyai-transcription\">AssemblyAI transcription</a></li>\n</ul>\n"}