{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-docs/voice-2.0/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"product":"Voice v2 (preview)","type":"markdown"},"seo":{"title":"Detect Voicemail & Beeps (AMD)","siteUrl":"https://developers.sinch.com","llmstxt":{"title":"Sinch Developer Documentation","description":"LLMs.txt containing a map of all the documentation files for Sinch.","sections":[{"title":"Numbers API","description":"The Numbers API enables you to search for, view, and activate numbers. It's considered a precursor to other APIs in the Sinch product family. The numbers API can be used in tandem with any of our APIs that perform messaging or calling.","includeFiles":["docs/numbers/**/*.md","docs/numbers/**/*.yaml"],"excludeFiles":["docs/numbers/index.md"]},{"title":"Conversation API","description":"Send and receive messages globally on many popular channels with ease and confidence when using Sinch's Conversation API. Conversation API is the preferred API for sending mobile messages on SMS and other social channels with Sinch. It is a simple API with unified error messages, consistent request payloads, and common webhook payloads that are channel-agnostic.","includeFiles":["docs/conversation/**/*.md","docs/conversation/**/*.yaml"],"excludeFiles":["docs/conversation/index.md"]},{"title":"Voice API","description":"The Voice API works as a big telephony switch. The Voice API handles incoming phone calls (also known as incoming call “legs”), sets up outgoing phone calls (or outgoing call “legs”), and bridges the two. The incoming call leg may come in over a data connection (from a smartphone or web application using the Sinch SDKs) or through a local phone number (from the PSTN network). Similarly, the outgoing call leg can be over data (to another smartphone or web application using the Sinch SDKs) or the PSTN network.","includeFiles":["docs/voice/**/*.md","docs/voice/**/*.yaml"],"excludeFiles":["docs/voice/index.md"]},{"title":"Voice API v2","description":"The Voice API works as a big telephony switch. The Voice API handles incoming phone calls (also known as incoming call “legs”), sets up outgoing phone calls (or outgoing call “legs”), and bridges the two. The incoming call leg may come in over a data connection (from a smartphone or web application using the Sinch SDKs) or through a local phone number (from the PSTN network). Similarly, the outgoing call leg can be over data (to another smartphone or web application using the Sinch SDKs) or the PSTN network.","includeFiles":["docs/voice-2.0/**/*.md","docs/voice-2.0/**/*.yaml"],"excludeFiles":["docs/voice-2.0/index.md"]},{"title":"Verification API","description":"The Verification API is a platform for phone number verification. It consists of the API and different software development kits (the Sinch SDKs) that you integrate with your smartphone or web application and cloud based back-end services. Together they enable SMS, Flashcall, Phone Call and Data verification in your application.","includeFiles":["docs/verification/**/*.md","docs/verification/**/*.yaml"],"excludeFiles":["docs/verification/index.md"]},{"title":"Provisioning API","description":"Provisioning API allows you to programmatically set up your senders, accounts and templates on your favorite messaging platforms on the Conversation API. For now, you can create your first WhatsApp channel through Meta's Embedded sign up, you can configure your first SMS App and configure your webhooks. As development continues, we will be adding the most commonly used channels.","includeFiles":["docs/provisioning-api/**/*.md","docs/provisioning-api/**/*.json"],"excludeFiles":["docs/provisioning-api/index.md"]},{"title":"Elastic SIP Trunking API","description":"With Elastic SIP Trunking you can create and manage your SIP trunks and phone numbers programmatically.","includeFiles":["docs/est/**/*.md","docs/est/**/*.yaml"],"excludeFiles":["docs/est/index.md"]},{"title":"Fax API","description":"Send and receive HIPAA compliant faxes on our modern fax platform using our developer-friendly API.","includeFiles":["docs/fax/**/*.md","docs/fax/**/*.yaml"],"excludeFiles":["docs/fax/index.md"]},{"title":"In-app Voice and Video SDK","description":"The In-app Voice and Video SDK enables you to add voice and video calling capabilities directly into your mobile or web application using the Sinch SDKs.","includeFiles":["docs/in-app-calling/**/*.md"],"excludeFiles":["docs/in-app-calling/index.md"]},{"title":"Number Lookup API","description":"The Number Lookup API is designed to provide in-depth information about phone numbers, helping enterprises enhance their communication strategies and prevent fraud. By identifying the type of phone line (for example, mobile, landline, VoIP) and the associated carrier, the API allows businesses to optimize routing, reduce unnecessary costs, and improve customer engagement.","includeFiles":["docs/number-lookup-api-v2/**/*.md","docs/number-lookup-api-v2/**/*.yaml"],"excludeFiles":["docs/number-lookup-api-v2/index.md"]},{"title":"Functions","description":"Serverless compute for voice and messaging. Deploy your code and Sinch routes live calls and messages to it — no infrastructure to run.","includeFiles":["docs/functions/functions/**/*.md","docs/functions/concepts/**/*.md","docs/functions/reference/**/*.md"],"excludeFiles":["docs/functions/functions/index.md"]},{"title":"CLI","description":"One command line for every Sinch API and the full Functions lifecycle — scaffold, run locally, deploy, and manage.","includeFiles":["docs/functions/cli/**/*.md"],"excludeFiles":["docs/functions/cli/index.md"]}]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"detect-voicemail--beeps-amd","__idx":0},"children":["Detect Voicemail & Beeps (AMD)"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Answering Machine Detection (AMD) lets you automatically distinguish between a live human and a voicemail machine, IVR, or beep when making outbound calls. The Voice API v2 runs AMD analysis on the call audio after the call is answered, then executes a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["different branch of SVAML commands"]}," depending on the result: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onHuman"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onMachine"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onBeep"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onUnknown"]},". You can wire a completely different call flow for each outcome, for example connect a human to a live agent, leave a voicemail when a beep is detected, or hang up silently on a machine."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["AMD is controlled by the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]}," SVAML command, which you place inline in the call payload or return from a webhook. The command is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["non-blocking"]},": the rest of your sequence keeps executing while detection runs in parallel, and the matching AMD branch fires as soon as a verdict is reached."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["There are two ways to observe a result:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["The AMD branch runs on the call itself."]}," Whatever commands you put under ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onHuman"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onBeep"]}," / etc. execute on the live call, so the human hears your greeting and the voicemail gets your message. This is audible on the answering phone with ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["no backend required"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["AMD result webhook events."]}," If your call runs on a service with a webhook URL, Sinch additionally POSTs a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.human"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.machine"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.beep"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.unknown"]}," event to that URL. This is how you get a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["visible log"]}," in your own server of which branch fired (see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/track-call-status"},"children":["Track Call Status"]}," for consuming call events)."]}]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The fastest path to a first success is the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["inline callout"]}," below: place the call, answer it, and hear the AMD branch run. Add the callback server later when you want server-side visibility or dynamic per-call AMD configuration."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"real-life-examples","__idx":2},"children":["Real-life examples"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Outreach campaigns"]},": leave a tailored voicemail only when a beep is detected, and connect a live answer to an agent."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Appointment reminders"]},": connect to a live agent when a human answers, leave a reminder message on voicemail systems."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Debt collection"]},": route live answers to a specialist, leave a callback number on answering machines."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Survey calls"]},": launch the survey IVR only when a real human is detected, abort silently on machine answers."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"setup-do-this-first","__idx":3},"children":["Setup (do this first)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["All scripts read their configuration from environment variables. ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Export"]}," them in the shell you will run from:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"export PROJECT_ID=...            # from https://dashboard.sinch.com\nexport KEY_ID=...\nexport KEY_SECRET=...\nexport SINCH_NUMBER=+1...        # your Sinch virtual number (E.164)\nexport DESTINATION_NUMBER=+1...  # the number to call (E.164)\n\n# Only needed for the callback-server path:\nexport CALLBACK_URL=...          # your public ngrok URL\nexport PORT=3000\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Notes:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Auth is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["HTTP Basic"]}," using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_ID:KEY_SECRET"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["These variables live only in the current shell. Open a new terminal and you will need to export them again (or add them to your shell profile)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["For the callback-server path you also need a public URL. Install ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://ngrok.com"},"children":["ngrok"]}," and a runtime (Node 18+, or Python 3.8+ with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["flask"]},")."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"first-success-inline-amd-callout-no-backend","__idx":4},"children":["First success: inline AMD callout (no backend)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This places an outbound call and runs AMD on it. The matching branch executes on the call, so you observe the result ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["by answering the phone"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pick a language below, paste the script, export your variables, and run it."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["What happens:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The script POSTs the full SVAML (including all four AMD branches) to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v2/projects/{projectId}/calls"]}," and prints ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AMD call created successfully"]}," with the returned ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]}," on ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["HTTP 201"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DESTINATION_NUMBER"]}," rings. Depending on how it is answered:",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["A person answers"]},", AMD resolves to human, the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onHuman"]}," branch plays ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["\"Hello! This is a call from Acme Corp...\""]}," then hangs up."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Voicemail picks up"]},", AMD waits for the beep, and on the tone the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onBeep"]}," branch plays ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["\"Hi, this is Acme Corp calling about your recent inquiry...\""]}," then hangs up."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Machine greeting, no beep yet"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onMachine"]}," hangs up silently."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Inconclusive"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onUnknown"]}," hangs up."]}]}]}]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Visibility note: the script's terminal output only confirms the call was ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["created"]}," (HTTP 201). It does ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not"]}," print which AMD branch fired, because that verdict lives on the call. To see the branch in your terminal, run the callback-server path further down and watch for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.*"]}," events."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"bash-curl","__idx":5},"children":["Bash (curl)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"#!/bin/bash\n# Sinch AMD Callout: outbound call with Answering Machine Detection (AMD).\n# AMD detects human vs. machine and fires different SVAML for each outcome.\n# Reads credentials from exported environment variables (see Setup).\n\nset -e\n\n: \"${PROJECT_ID:?ERROR: PROJECT_ID is not set. Export it first.}\"\n: \"${KEY_ID:?ERROR: KEY_ID is not set.}\"\n: \"${KEY_SECRET:?ERROR: KEY_SECRET is not set.}\"\n: \"${SINCH_NUMBER:?ERROR: SINCH_NUMBER is not set.}\"\n: \"${DESTINATION_NUMBER:?ERROR: DESTINATION_NUMBER is not set.}\"\n\nBASE_URL=\"https://voice.api.sinch.com/v2\"\n\necho \"Placing AMD callout from ${SINCH_NUMBER} to ${DESTINATION_NUMBER} ...\"\n\nBODY=$(printf '{\n  \"commands\": [\n    {\n      \"command\": \"dial\",\n      \"callName\": \"amd-call\",\n      \"from\": { \"type\": \"PHONE\", \"phone\": { \"number\": \"%s\" } },\n      \"to\":   { \"type\": \"PHONE\", \"phone\": { \"number\": \"%s\" } },\n      \"dialTimeoutDurationSeconds\": 45,\n      \"maxCallDurationSeconds\": 300,\n      \"events\": {\n        \"onAnswer\": [\n          {\n            \"command\": \"amd\",\n            \"events\": {\n              \"onHuman\": [\n                {\n                  \"command\": \"messages\",\n                  \"messagesName\": \"human-greeting\",\n                  \"messages\": [\n                    {\n                      \"type\": \"SAY\",\n                      \"say\": {\n                        \"text\": \"Hello! This is a call from Acme Corp. An agent will be with you shortly.\",\n                        \"voiceName\": \"Emma\"\n                      }\n                    }\n                  ],\n                  \"events\": { \"onFinish\": [{ \"command\": \"hangup\" }] }\n                }\n              ],\n              \"onMachine\": [\n                { \"command\": \"hangup\" }\n              ],\n              \"onBeep\": [\n                {\n                  \"command\": \"messages\",\n                  \"messagesName\": \"voicemail-message\",\n                  \"messages\": [\n                    {\n                      \"type\": \"SAY\",\n                      \"say\": {\n                        \"text\": \"Hi, this is Acme Corp calling about your recent inquiry. Please call us back at 555-1234. Thank you.\",\n                        \"voiceName\": \"Emma\"\n                      }\n                    }\n                  ],\n                  \"events\": { \"onFinish\": [{ \"command\": \"hangup\" }] }\n                }\n              ],\n              \"onUnknown\": [\n                { \"command\": \"hangup\" }\n              ]\n            }\n          }\n        ]\n      }\n    }\n  ]\n}' \"${SINCH_NUMBER}\" \"${DESTINATION_NUMBER}\")\n\nRESPONSE=$(curl -s -w \"\\n%{http_code}\" \\\n  -X POST \\\n  -u \"${KEY_ID}:${KEY_SECRET}\" \\\n  \"${BASE_URL}/projects/${PROJECT_ID}/calls\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"${BODY}\")\n\nHTTP_BODY=$(echo \"${RESPONSE}\" | head -n -1)\nHTTP_CODE=$(echo \"${RESPONSE}\" | tail -n 1)\n\nif [ \"${HTTP_CODE}\" -eq 201 ]; then\n  echo \"AMD call created successfully (HTTP ${HTTP_CODE}):\"\n  echo \"${HTTP_BODY}\" | (command -v jq > /dev/null && jq '.' || cat)\nelse\n  echo \"ERROR: API returned HTTP ${HTTP_CODE}:\" >&2\n  echo \"${HTTP_BODY}\" >&2\n  exit 1\nfi\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"python","__idx":6},"children":["Python"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Requires ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pip install requests"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"# Sinch AMD Callout: outbound call with Answering Machine Detection (AMD).\n# AMD detects human vs. machine and executes different SVAML for each outcome.\n# Reads credentials from exported environment variables (see Setup).\n# Requirements: pip install requests\n\nimport os\nimport sys\nimport json\nimport requests\n\nproject_id         = os.environ.get(\"PROJECT_ID\")\nkey_id             = os.environ.get(\"KEY_ID\")\nkey_secret         = os.environ.get(\"KEY_SECRET\")\nsinch_number       = os.environ.get(\"SINCH_NUMBER\")\ndestination_number = os.environ.get(\"DESTINATION_NUMBER\")\n\nfor var, name in [\n    (project_id, \"PROJECT_ID\"), (key_id, \"KEY_ID\"), (key_secret, \"KEY_SECRET\"),\n    (sinch_number, \"SINCH_NUMBER\"), (destination_number, \"DESTINATION_NUMBER\"),\n]:\n    if not var:\n        print(f\"ERROR: {name} is not set. Export it first.\", file=sys.stderr)\n        sys.exit(1)\n\nurl = f\"https://voice.api.sinch.com/v2/projects/{project_id}/calls\"\n\n# The `amd` command must be placed in the `onAnswer` event of a `dial` command.\n# It fires different SVAML commands based on what AMD detects.\npayload = {\n    \"commands\": [\n        {\n            \"command\": \"dial\",\n            \"callName\": \"amd-call\",\n            \"from\": {\"type\": \"PHONE\", \"phone\": {\"number\": sinch_number}},\n            \"to\":   {\"type\": \"PHONE\", \"phone\": {\"number\": destination_number}},\n            \"dialTimeoutDurationSeconds\": 45,\n            \"maxCallDurationSeconds\": 300,\n            \"events\": {\n                \"onAnswer\": [\n                    {\n                        \"command\": \"amd\",\n                        \"events\": {\n                            # Human picked up: play a personalized greeting\n                            \"onHuman\": [\n                                {\n                                    \"command\": \"messages\",\n                                    \"messagesName\": \"human-greeting\",\n                                    \"messages\": [\n                                        {\n                                            \"type\": \"SAY\",\n                                            \"say\": {\n                                                \"text\": \"Hello! This is a call from Acme Corp. An agent will be with you shortly.\",\n                                                \"voiceName\": \"Emma\"\n                                            }\n                                        }\n                                    ],\n                                    \"events\": {\n                                        \"onFinish\": [{\"command\": \"hangup\"}]\n                                    }\n                                }\n                            ],\n                            # Machine greeting detected (beep not yet heard): just hang up\n                            \"onMachine\": [\n                                {\"command\": \"hangup\"}\n                            ],\n                            # Beep detected: leave a voicemail message right now\n                            \"onBeep\": [\n                                {\n                                    \"command\": \"messages\",\n                                    \"messagesName\": \"voicemail-message\",\n                                    \"messages\": [\n                                        {\n                                            \"type\": \"SAY\",\n                                            \"say\": {\n                                                \"text\": \"Hi, this is Acme Corp calling about your recent inquiry. Please call us back at 555-1234. Thank you.\",\n                                                \"voiceName\": \"Emma\"\n                                            }\n                                        }\n                                    ],\n                                    \"events\": {\n                                        \"onFinish\": [{\"command\": \"hangup\"}]\n                                    }\n                                }\n                            ],\n                            # Unknown: hang up safely\n                            \"onUnknown\": [\n                                {\"command\": \"hangup\"}\n                            ]\n                        }\n                    }\n                ]\n            }\n        }\n    ]\n}\n\nprint(f\"Placing AMD callout from {sinch_number} to {destination_number} ...\")\n\ntry:\n    response = requests.post(url, json=payload, auth=(key_id, key_secret))\n    data = response.json()\n\n    if response.status_code == 201:\n        print(\"AMD call created successfully:\")\n        print(json.dumps(data, indent=2))\n    else:\n        print(f\"ERROR {response.status_code}:\", file=sys.stderr)\n        print(json.dumps(data, indent=2), file=sys.stderr)\n        sys.exit(1)\n\nexcept requests.RequestException as e:\n    print(f\"Request failed: {e}\", file=sys.stderr)\n    sys.exit(1)\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"nodejs-native-fetch-node-18","__idx":7},"children":["Node.js (native fetch, Node 18+)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Save as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd-callout.mjs"]}," (or use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"type\": \"module\""]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["package.json"]},") so top-level ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["await"]}," works."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// Sinch AMD Callout: Node.js (native fetch, Node 18+) outbound call with AMD.\n// Reads credentials from exported environment variables (see Setup).\n// Run: node amd-callout.mjs\n\nconst projectId         = process.env.PROJECT_ID         || (() => { throw new Error(\"PROJECT_ID not set\"); })();\nconst keyId             = process.env.KEY_ID             || (() => { throw new Error(\"KEY_ID not set\"); })();\nconst keySecret         = process.env.KEY_SECRET         || (() => { throw new Error(\"KEY_SECRET not set\"); })();\nconst sinchNumber       = process.env.SINCH_NUMBER       || (() => { throw new Error(\"SINCH_NUMBER not set\"); })();\nconst destinationNumber = process.env.DESTINATION_NUMBER || (() => { throw new Error(\"DESTINATION_NUMBER not set\"); })();\n\nconst authHeader = \"Basic \" + Buffer.from(`${keyId}:${keySecret}`).toString(\"base64\");\n\nconst payload = {\n  commands: [\n    {\n      command: \"dial\",\n      callName: \"amd-call\",\n      from: { type: \"PHONE\", phone: { number: sinchNumber } },\n      to:   { type: \"PHONE\", phone: { number: destinationNumber } },\n      dialTimeoutDurationSeconds: 45,\n      maxCallDurationSeconds: 300,\n      events: {\n        onAnswer: [\n          {\n            command: \"amd\",\n            events: {\n              // Human detected: greet and connect to an agent (or take further action)\n              onHuman: [\n                {\n                  command: \"messages\",\n                  messagesName: \"human-greeting\",\n                  messages: [\n                    {\n                      type: \"SAY\",\n                      say: {\n                        text: \"Hello! This is a call from Acme Corp. An agent will be with you shortly.\",\n                        voiceName: \"Emma\"\n                      }\n                    }\n                  ],\n                  events: { onFinish: [{ command: \"hangup\" }] }\n                }\n              ],\n              // Machine detected (no beep yet): hang up silently\n              onMachine: [{ command: \"hangup\" }],\n              // Beep detected: play the voicemail message right after the beep\n              onBeep: [\n                {\n                  command: \"messages\",\n                  messagesName: \"voicemail-message\",\n                  messages: [\n                    {\n                      type: \"SAY\",\n                      say: {\n                        text: \"Hi, this is Acme Corp calling about your recent inquiry. Please call us back at 555-1234. Thank you.\",\n                        voiceName: \"Emma\"\n                      }\n                    }\n                  ],\n                  events: { onFinish: [{ command: \"hangup\" }] }\n                }\n              ],\n              // Unknown: safe default is to hang up\n              onUnknown: [{ command: \"hangup\" }]\n            }\n          }\n        ]\n      }\n    }\n  ]\n};\n\nconsole.log(`Placing AMD callout from ${sinchNumber} to ${destinationNumber} ...`);\n\nconst response = await fetch(\n  `https://voice.api.sinch.com/v2/projects/${projectId}/calls`,\n  {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n      Authorization: authHeader\n    },\n    body: JSON.stringify(payload)\n  }\n);\n\nconst data = await response.json();\n\nif (response.status === 201) {\n  console.log(\"AMD call created successfully:\", JSON.stringify(data, null, 2));\n} else {\n  console.error(`ERROR ${response.status}:`, JSON.stringify(data, null, 2));\n  process.exit(1);\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"php","__idx":8},"children":["PHP"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Requires PHP 8+ with the curl extension. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getenv()"]}," reads the exported variables directly."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"php","header":{"controls":{"copy":{}}},"source":"<?php\n// Sinch AMD Callout: PHP outbound call with Answering Machine Detection.\n// Reads credentials from exported environment variables (see Setup).\n// Requirements: PHP 8+ with the curl extension.\n\n$projectId         = getenv('PROJECT_ID')         ?: die(\"ERROR: PROJECT_ID not set.\\n\");\n$keyId             = getenv('KEY_ID')             ?: die(\"ERROR: KEY_ID not set.\\n\");\n$keySecret         = getenv('KEY_SECRET')         ?: die(\"ERROR: KEY_SECRET not set.\\n\");\n$sinchNumber       = getenv('SINCH_NUMBER')       ?: die(\"ERROR: SINCH_NUMBER not set.\\n\");\n$destinationNumber = getenv('DESTINATION_NUMBER') ?: die(\"ERROR: DESTINATION_NUMBER not set.\\n\");\n\n$url = \"https://voice.api.sinch.com/v2/projects/{$projectId}/calls\";\n\n// The `amd` command must be inside `onAnswer`. AMD fires different SVAML\n// depending on whether a human, machine, or beep is detected.\n$payload = [\n    'commands' => [\n        [\n            'command' => 'dial',\n            'callName' => 'amd-call',\n            'from'    => ['type' => 'PHONE', 'phone' => ['number' => $sinchNumber]],\n            'to'      => ['type' => 'PHONE', 'phone' => ['number' => $destinationNumber]],\n            'dialTimeoutDurationSeconds' => 45,\n            'maxCallDurationSeconds' => 300,\n            'events' => [\n                'onAnswer' => [\n                    [\n                        'command' => 'amd',\n                        'events'  => [\n                            // Human detected: play a personalized greeting\n                            'onHuman' => [\n                                [\n                                    'command'       => 'messages',\n                                    'messagesName'  => 'human-greeting',\n                                    'messages' => [\n                                        [\n                                            'type' => 'SAY',\n                                            'say'  => [\n                                                'text'      => 'Hello! This is a call from Acme Corp. An agent will be with you shortly.',\n                                                'voiceName' => 'Emma',\n                                            ],\n                                        ],\n                                    ],\n                                    'events' => [\n                                        'onFinish' => [['command' => 'hangup']],\n                                    ],\n                                ],\n                            ],\n                            // Machine greeting (no beep yet): hang up\n                            'onMachine' => [\n                                ['command' => 'hangup'],\n                            ],\n                            // Beep detected: leave voicemail immediately after the beep\n                            'onBeep' => [\n                                [\n                                    'command'       => 'messages',\n                                    'messagesName'  => 'voicemail-message',\n                                    'messages' => [\n                                        [\n                                            'type' => 'SAY',\n                                            'say'  => [\n                                                'text'      => 'Hi, this is Acme Corp calling about your recent inquiry. Please call us back at 555-1234. Thank you.',\n                                                'voiceName' => 'Emma',\n                                            ],\n                                        ],\n                                    ],\n                                    'events' => [\n                                        'onFinish' => [['command' => 'hangup']],\n                                    ],\n                                ],\n                            ],\n                            // Unknown result: hang up safely\n                            'onUnknown' => [\n                                ['command' => 'hangup'],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ],\n];\n\necho \"Placing AMD callout from {$sinchNumber} to {$destinationNumber} ...\\n\";\n\n$ch = curl_init($url);\ncurl_setopt_array($ch, [\n    CURLOPT_POST           => true,\n    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],\n    CURLOPT_USERPWD        => \"{$keyId}:{$keySecret}\",\n    CURLOPT_POSTFIELDS     => json_encode($payload),\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$responseBody = curl_exec($ch);\n$httpCode     = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n$curlError    = curl_error($ch);\ncurl_close($ch);\n\nif ($curlError) {\n    fwrite(STDERR, \"curl error: {$curlError}\\n\");\n    exit(1);\n}\n\n$data = json_decode($responseBody, true);\n\nif ($httpCode === 201) {\n    echo \"AMD call created successfully:\\n\";\n    echo json_encode($data, JSON_PRETTY_PRINT) . \"\\n\";\n} else {\n    fwrite(STDERR, \"ERROR {$httpCode}:\\n\");\n    fwrite(STDERR, json_encode($data, JSON_PRETTY_PRINT) . \"\\n\");\n    exit(1);\n}\n","lang":"php"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"java-11","__idx":9},"children":["Java (11+)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["System.getenv"]}," reads the exported variables directly."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"// Sinch AMD Callout: Java outbound call with Answering Machine Detection.\n// Requires Java 11+ (java.net.http.HttpClient).\n// Reads credentials from exported environment variables (see Setup).\n//\n// Compile: javac -d out AmdCallout.java\n// Run:     java -cp out com.sinch.tutorials.amd.AmdCallout\n\npackage com.sinch.tutorials.amd;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.Base64;\n\npublic class AmdCallout {\n\n    public static void main(String[] args) throws Exception {\n        String projectId         = requireEnv(\"PROJECT_ID\");\n        String keyId             = requireEnv(\"KEY_ID\");\n        String keySecret         = requireEnv(\"KEY_SECRET\");\n        String sinchNumber       = requireEnv(\"SINCH_NUMBER\");\n        String destinationNumber = requireEnv(\"DESTINATION_NUMBER\");\n\n        String url         = \"https://voice.api.sinch.com/v2/projects/\" + projectId + \"/calls\";\n        String credentials = Base64.getEncoder()\n                .encodeToString((keyId + \":\" + keySecret).getBytes());\n\n        // The `amd` command must be inside `onAnswer`.\n        // AMD fires different SVAML depending on detection result.\n        String body = String.format(\"\"\"\n            {\n              \"commands\": [\n                {\n                  \"command\": \"dial\",\n                  \"callName\": \"amd-call\",\n                  \"from\": { \"type\": \"PHONE\", \"phone\": { \"number\": \"%s\" } },\n                  \"to\":   { \"type\": \"PHONE\", \"phone\": { \"number\": \"%s\" } },\n                  \"dialTimeoutDurationSeconds\": 45,\n                  \"maxCallDurationSeconds\": 300,\n                  \"events\": {\n                    \"onAnswer\": [\n                      {\n                        \"command\": \"amd\",\n                        \"events\": {\n                          \"onHuman\": [\n                            {\n                              \"command\": \"messages\",\n                              \"messagesName\": \"human-greeting\",\n                              \"messages\": [\n                                {\n                                  \"type\": \"SAY\",\n                                  \"say\": {\n                                    \"text\": \"Hello! This is a call from Acme Corp. An agent will be with you shortly.\",\n                                    \"voiceName\": \"Emma\"\n                                  }\n                                }\n                              ],\n                              \"events\": { \"onFinish\": [{ \"command\": \"hangup\" }] }\n                            }\n                          ],\n                          \"onMachine\": [\n                            { \"command\": \"hangup\" }\n                          ],\n                          \"onBeep\": [\n                            {\n                              \"command\": \"messages\",\n                              \"messagesName\": \"voicemail-message\",\n                              \"messages\": [\n                                {\n                                  \"type\": \"SAY\",\n                                  \"say\": {\n                                    \"text\": \"Hi, this is Acme Corp calling about your recent inquiry. Please call us back at 555-1234. Thank you.\",\n                                    \"voiceName\": \"Emma\"\n                                  }\n                                }\n                              ],\n                              \"events\": { \"onFinish\": [{ \"command\": \"hangup\" }] }\n                            }\n                          ],\n                          \"onUnknown\": [\n                            { \"command\": \"hangup\" }\n                          ]\n                        }\n                      }\n                    ]\n                  }\n                }\n              ]\n            }\n            \"\"\", sinchNumber, destinationNumber);\n\n        System.out.println(\"Placing AMD callout from \" + sinchNumber + \" to \" + destinationNumber + \" ...\");\n\n        HttpClient client = HttpClient.newHttpClient();\n        HttpRequest request = HttpRequest.newBuilder()\n                .uri(URI.create(url))\n                .header(\"Content-Type\", \"application/json\")\n                .header(\"Authorization\", \"Basic \" + credentials)\n                .POST(HttpRequest.BodyPublishers.ofString(body))\n                .build();\n\n        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n        int statusCode = response.statusCode();\n\n        if (statusCode == 201) {\n            System.out.println(\"AMD call created successfully:\");\n            System.out.println(response.body());\n        } else {\n            System.err.println(\"ERROR \" + statusCode + \":\");\n            System.err.println(response.body());\n            System.exit(1);\n        }\n    }\n\n    private static String requireEnv(String name) {\n        String value = System.getenv(name);\n        if (value == null || value.isBlank()) {\n            System.err.println(\"ERROR: \" + name + \" is not set. Export it first.\");\n            System.exit(1);\n        }\n        return value;\n    }\n}\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"browser-javascript-demo-only","__idx":10},"children":["Browser JavaScript (demo only)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Calling the Sinch API directly from a browser hits CORS restrictions, so this is a copy-paste template rather than a runnable script. It ships with placeholder strings you must replace, and in production you should proxy the call through your own backend."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// Sinch AMD Callout: browser JS to trigger an outbound call with AMD.\n// Note: calling the Sinch API directly from a browser will hit CORS restrictions.\n// In production, proxy these calls through your backend.\n// Replace the placeholders below; this file will not run as-is.\n\n(async function sinchAmdCallout() {\n  const projectId         = \"YOUR_PROJECT_ID\";\n  const keyId             = \"YOUR_KEY_ID\";\n  const keySecret         = \"YOUR_KEY_SECRET\";\n  const sinchNumber       = \"+1XXXXXXXXXX\";\n  const destinationNumber = \"+1YYYYYYYYYY\";\n\n  const baseUrl    = \"https://voice.api.sinch.com/v2\";\n  const authHeader = \"Basic \" + btoa(`${keyId}:${keySecret}`);\n\n  const payload = {\n    commands: [\n      {\n        command: \"dial\",\n        callName: \"amd-call\",\n        from: { type: \"PHONE\", phone: { number: sinchNumber } },\n        to:   { type: \"PHONE\", phone: { number: destinationNumber } },\n        dialTimeoutDurationSeconds: 45,\n        maxCallDurationSeconds: 300,\n        events: {\n          onAnswer: [\n            {\n              command: \"amd\",\n              events: {\n                onHuman: [\n                  {\n                    command: \"messages\",\n                    messagesName: \"human-greeting\",\n                    messages: [\n                      {\n                        type: \"SAY\",\n                        say: {\n                          text: \"Hello! This is a call from Acme Corp. An agent will be with you shortly.\",\n                          voiceName: \"Emma\"\n                        }\n                      }\n                    ],\n                    events: { onFinish: [{ command: \"hangup\" }] }\n                  }\n                ],\n                onMachine: [{ command: \"hangup\" }],\n                onBeep: [\n                  {\n                    command: \"messages\",\n                    messagesName: \"voicemail-message\",\n                    messages: [\n                      {\n                        type: \"SAY\",\n                        say: {\n                          text: \"Hi, this is Acme Corp calling about your recent inquiry. Please call us back at 555-1234. Thank you.\",\n                          voiceName: \"Emma\"\n                        }\n                      }\n                    ],\n                    events: { onFinish: [{ command: \"hangup\" }] }\n                  }\n                ],\n                onUnknown: [{ command: \"hangup\" }]\n              }\n            }\n          ]\n        }\n      }\n    ]\n  };\n\n  console.log(`Placing AMD callout from ${sinchNumber} to ${destinationNumber} ...`);\n\n  const response = await fetch(\n    `${baseUrl}/projects/${projectId}/calls`,\n    {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        Authorization: authHeader\n      },\n      body: JSON.stringify(payload)\n    }\n  );\n\n  const data = await response.json();\n\n  if (response.status === 201) {\n    console.log(\"AMD call created successfully:\", data);\n  } else {\n    console.error(`ERROR ${response.status}:`, data);\n  }\n})();\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"place-amd-inside-an-outbound-dial-the-contract","__idx":11},"children":["Place AMD inside an outbound dial (the contract)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]}," as the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["first"]}," command in the dial's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onAnswer"]}," event:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"commands\": [\n    {\n      \"command\": \"dial\",\n      \"callName\": \"amd-call\",\n      \"from\": { \"type\": \"PHONE\", \"phone\": { \"number\": \"+1SINCH_NUMBER\" } },\n      \"to\":   { \"type\": \"PHONE\", \"phone\": { \"number\": \"+1DESTINATION\" } },\n      \"dialTimeoutDurationSeconds\": 45,\n      \"maxCallDurationSeconds\": 300,\n      \"events\": {\n        \"onAnswer\": [\n          { \"command\": \"amd\", \"events\": { /* onHuman / onMachine / onBeep / onUnknown */ } }\n        ]\n      }\n    }\n  ]\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The four AMD branches (verified against the spec's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amdEvents"]}," schema):"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onHuman"]},": a live person picked up. Connect to an agent or start a conversation."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onMachine"]},": a machine answered and is playing its greeting (beep not yet detected). Usually hang up or wait for the beep."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onBeep"]},": a voicemail beep was detected. Leave your voicemail message now."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onUnknown"]},": detection was inconclusive. Safe default is to hang up, or treat as human."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each branch holds a normal array of SVAML commands. All four are ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["optional"]},": branches you leave out are simply not executed, so you can wire only the outcomes you care about."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Per the spec, the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]}," command has ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["no fields besides ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["command"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["events"]}]},". There are no tuning knobs (no timeouts, no sensitivity) on the command itself."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"dynamic-amd-via-a-callback-server","__idx":12},"children":["Dynamic AMD via a callback server"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use this when you want per-call AMD configuration (for example, choosing the voicemail script by destination) or when you want ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["server-side logs"]}," of the AMD verdict. The callback server answers inbound calls and returns SVAML that runs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]},", and it logs the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.*"]}," result events Sinch posts back."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"1-start-the-server-and-expose-it","__idx":13},"children":["1. Start the server and expose it"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Paste one of the servers below, export your variables, then run it and expose it with ngrok:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"node amd-callback-server.mjs   # Express, port 3000\n# or\npython amd-callback-server.py  # Flask, port 3000\n\nngrok http 3000\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Copy the ngrok HTTPS URL into your service's webhook configuration (set the service ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callBehavior"]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WEBHOOK"]},", see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/inbound-pstn"},"children":["Inbound PSTN Calls"]}," for the exact PATCH request). The server's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/webhook"]}," endpoint receives the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.incoming"]}," event and responds with SVAML that answers the call and runs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["There is no wrapper command in the webhook response: the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["commands"]}," array is returned at the top level, starting with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["answer"]},", then ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]}," as the first command after answering."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"2-nodejs-server-express","__idx":14},"children":["2. Node.js server (Express)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Requires ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm install express"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"type\": \"module\""]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["package.json"]}," (or a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".mjs"]}," extension)."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// Sinch AMD Callback Server: Express.js webhook server.\n// Handles inbound call events, responds with SVAML including the AMD command,\n// and logs the AMD verdict events Sinch posts back.\n// Reads config from exported environment variables (see Setup).\n//\n// Requirements: npm install express\n// Run: node amd-callback-server.mjs\n\nimport express from \"express\";\n\nconst sinchNumber       = process.env.SINCH_NUMBER       || (() => { throw new Error(\"SINCH_NUMBER not set\"); })();\nconst destinationNumber = process.env.DESTINATION_NUMBER || (() => { throw new Error(\"DESTINATION_NUMBER not set\"); })();\nconst PORT = process.env.PORT || 3000;\n\nconst app = express();\napp.use(express.json());\n\n// POST /webhook: receives Sinch call events and responds with AMD SVAML\napp.post(\"/webhook\", (req, res) => {\n  const event = req.body?.event;\n  const call  = req.body?.call;\n\n  console.log(`Received event: ${event}`, call?.callId);\n\n  if (event === \"call.incoming\") {\n    // Inbound call: respond with SVAML to answer and run AMD detection.\n    // Return the commands directly at the top level: `answer` first, then\n    // `amd` as the first command after answering. AMD then fires one of:\n    // onHuman, onMachine, onBeep, onUnknown.\n    const svamlResponse = {\n      commands: [\n        { command: \"answer\" },\n        // Run AMD detection as the first thing after answering\n        {\n          command: \"amd\",\n          events: {\n            // Live human detected: play a personalized message\n            onHuman: [\n              {\n                command: \"messages\",\n                messagesName: \"human-greeting\",\n                messages: [\n                  {\n                    type: \"SAY\",\n                    say: {\n                      text: \"Hello! This is a call from Acme Corp. An agent will be with you shortly.\",\n                      voiceName: \"Emma\"\n                    }\n                  }\n                ],\n                events: { onFinish: [{ command: \"hangup\" }] }\n              }\n            ],\n\n            // Machine greeting detected (waiting for beep): hang up silently\n            onMachine: [{ command: \"hangup\" }],\n\n            // Beep detected: leave voicemail right after the beep tone\n            onBeep: [\n              {\n                command: \"messages\",\n                messagesName: \"voicemail-message\",\n                messages: [\n                  {\n                    type: \"SAY\",\n                    say: {\n                      text: \"Hi, this is Acme Corp calling about your recent inquiry. Please call us back at 555-1234. Thank you.\",\n                      voiceName: \"Emma\"\n                    }\n                  }\n                ],\n                events: { onFinish: [{ command: \"hangup\" }] }\n              }\n            ],\n\n            // AMD inconclusive: hang up rather than risk a bad experience\n            onUnknown: [{ command: \"hangup\" }]\n          }\n        }\n      ]\n    };\n\n    return res.status(200).json(svamlResponse);\n  }\n\n  // AMD verdict events: this is how you see which branch fired, server-side.\n  if (event?.startsWith(\"call.amd.\")) {\n    console.log(`AMD verdict: ${event}`);         // call.amd.human, call.amd.beep, ...\n    return res.status(200).json({ commands: [] }); // no further SVAML needed\n  }\n\n  // Acknowledge all other events\n  console.log(`Unhandled event: ${event}`);\n  res.status(200).json({ commands: [] });\n});\n\napp.listen(PORT, () => {\n  console.log(`AMD callback server listening on port ${PORT}`);\n  console.log(`Set your Sinch service webhook URL to: http://localhost:${PORT}/webhook`);\n  console.log(`(Use ngrok: ngrok http ${PORT})`);\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"3-python-server-flask","__idx":15},"children":["3. Python server (Flask)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Requires ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pip install flask"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"# Sinch AMD Callback Server: Flask webhook server.\n# Handles inbound call events, responds with SVAML including the AMD command,\n# and logs the AMD verdict events Sinch posts back.\n# Reads config from exported environment variables (see Setup).\n# Requirements: pip install flask\n\nimport os\nimport sys\nfrom flask import Flask, request, jsonify\n\nsinch_number       = os.environ.get(\"SINCH_NUMBER\")\ndestination_number = os.environ.get(\"DESTINATION_NUMBER\")\n\nfor var, name in [(sinch_number, \"SINCH_NUMBER\"), (destination_number, \"DESTINATION_NUMBER\")]:\n    if not var:\n        print(f\"ERROR: {name} is not set. Export it first.\", file=sys.stderr)\n        sys.exit(1)\n\nport = int(os.environ.get(\"PORT\", 3000))\napp  = Flask(__name__)\n\n\n@app.route(\"/webhook\", methods=[\"POST\"])\ndef webhook():\n    \"\"\"Handles Sinch call events and responds with AMD SVAML.\"\"\"\n    body  = request.get_json(force=True)\n    event = body.get(\"event\")\n    call  = body.get(\"call\", {})\n\n    print(f\"Received event: {event}, callId: {call.get('callId')}\")\n\n    if event == \"call.incoming\":\n        # Inbound call: answer and run AMD.\n        # Return the commands directly at the top level: `answer` first, then\n        # `amd` as the first command after answering. AMD fires one of:\n        # onHuman, onMachine, onBeep, onUnknown.\n        svaml_response = {\n            \"commands\": [\n                {\"command\": \"answer\"},\n                # AMD detection: first command after answering\n                {\n                    \"command\": \"amd\",\n                    \"events\": {\n                        # Live human: play a personalized greeting\n                        \"onHuman\": [\n                            {\n                                \"command\": \"messages\",\n                                \"messagesName\": \"human-greeting\",\n                                \"messages\": [\n                                    {\n                                        \"type\": \"SAY\",\n                                        \"say\": {\n                                            \"text\": \"Hello! This is a call from Acme Corp. An agent will be with you shortly.\",\n                                            \"voiceName\": \"Emma\"\n                                        }\n                                    }\n                                ],\n                                \"events\": {\"onFinish\": [{\"command\": \"hangup\"}]}\n                            }\n                        ],\n\n                        # Machine greeting (no beep yet): hang up silently\n                        \"onMachine\": [{\"command\": \"hangup\"}],\n\n                        # Beep detected: leave voicemail right after the beep\n                        \"onBeep\": [\n                            {\n                                \"command\": \"messages\",\n                                \"messagesName\": \"voicemail-message\",\n                                \"messages\": [\n                                    {\n                                        \"type\": \"SAY\",\n                                        \"say\": {\n                                            \"text\": \"Hi, this is Acme Corp calling about your recent inquiry. Please call us back at 555-1234. Thank you.\",\n                                            \"voiceName\": \"Emma\"\n                                        }\n                                    }\n                                ],\n                                \"events\": {\"onFinish\": [{\"command\": \"hangup\"}]}\n                            }\n                        ],\n\n                        # Unknown result: hang up safely\n                        \"onUnknown\": [{\"command\": \"hangup\"}]\n                    }\n                }\n            ]\n        }\n        return jsonify(svaml_response), 200\n\n    # AMD verdict events: this is how you see which branch fired, server-side.\n    if event and event.startswith(\"call.amd.\"):\n        print(f\"AMD verdict: {event}\")            # call.amd.human, call.amd.beep, ...\n        return jsonify({\"commands\": []}), 200      # no further SVAML needed\n\n    print(f\"Unhandled event: {event}\")\n    return jsonify({\"commands\": []}), 200\n\n\nif __name__ == \"__main__\":\n    print(f\"AMD callback server listening on port {port}\")\n    print(f\"Set your Sinch service webhook URL to: http://localhost:{port}/webhook\")\n    print(f\"(Use ngrok: ngrok http {port})\")\n    app.run(host=\"0.0.0.0\", port=port)\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"4-see-the-amd-verdict-in-your-logs","__idx":16},"children":["4. See the AMD verdict in your logs"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Both servers above include a handler for the AMD result events. Sinch posts the verdict back to the same webhook URL as one of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.human"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.machine"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.beep"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.unknown"]}," (verified event names). With the handler in place, answering as a human logs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AMD verdict: call.amd.human"]},", and a voicemail logs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.machine"]}," then ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.beep"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Webhook delivery uses CloudEvents (HTTP binary content mode, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ce-type: com.sinch.voice.call.control.v1"]},"); the JSON body is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ event, call }"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-success-looks-like","__idx":17},"children":["What success looks like"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Scenario"},"children":["Scenario"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"On the answering phone"},"children":["On the answering phone"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"In your callback log"},"children":["In your callback log"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Human answers"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Hears the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onHuman"]}," greeting"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.human"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Voicemail"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Beep, then hears the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onBeep"]}," voicemail message"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.machine"]}," then ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.beep"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Machine, no beep"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Silence then hangup"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.machine"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Inconclusive"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Hangup"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.unknown"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The inline-callout terminal only ever prints ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AMD call created successfully"]}," (HTTP 201). That confirms the call was placed, not which branch ran. Use the callback server for the branch verdict."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"tips-for-amd-accuracy","__idx":18},"children":["Tips for AMD accuracy"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Allow ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dialTimeoutDurationSeconds"]}," of at least ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["30"]},", because machines may take a few seconds to answer. The scripts use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["45"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Make ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]}," the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["first"]}," command in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onAnswer"]},". Do not play messages before AMD runs, since they bias detection."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onBeep"]}," is the right moment to start your voicemail message, not ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onMachine"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If you want hold tones during analysis, play them on a separate call leg, not the leg being analysed."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"language-coverage","__idx":19},"children":["Language coverage"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Six callout variants are shown above, plus two callback-server variants. All send an ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["identical AMD payload"]}," (same four branches, same ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["voiceName: Emma"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dialTimeoutDurationSeconds: 45"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCallDurationSeconds: 300"]},"). Every server-side variant reads credentials from ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["exported environment variables"]},", so export your variables in the same shell before running."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Variant"},"children":["Variant"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Run"},"children":["Run"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Notes"},"children":["Notes"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Bash"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bash amd-callout.sh"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["curl + Basic auth, pretty-prints with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jq"]}," if present."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Python"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["python amd-callout.py"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pip install requests"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Node.js"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node amd-callout.mjs"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Node 18+ native ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetch"]},", save as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".mjs"]}," or set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"type\": \"module\""]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Browser JS"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["(browser)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Demo only, has placeholder credentials and hits CORS. Proxy through a backend in production."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["PHP"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["php amd-callout.php"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["PHP 8+ with curl. Reads env via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["getenv()"]},", so export the vars first."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Java"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["javac -d out AmdCallout.java && java -cp out com.sinch.tutorials.amd.AmdCallout"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Java 11+. Reads ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["System.getenv"]},", so export the vars first."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Node callback server"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["node amd-callback-server.mjs"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Express, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm install express"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Python callback server"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["python amd-callback-server.py"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Flask, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pip install flask"]},"."]}]}]}]}]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The five runnable callouts (Bash, Python, Node.js, PHP, Java) all read credentials from the exported environment. The browser JS variant is the odd one out: it uses placeholder strings and will not run as-is, since a browser has no environment variables to export and calling the API directly hits CORS. The Node.js callout and the browser JS are intentionally different (server-side Node vs browser), not duplicates."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-amd-command-at-a-glance","__idx":20},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]}," command at a glance"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amd"]}," is a non-blocking SVAML command with ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["only"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["command"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["events"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["events"]}," holds optional ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onHuman"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onMachine"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onBeep"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onUnknown"]}," arrays, each a list of SVAML commands."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Its natural home is the first command after the call is answered: inside ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dial.events.onAnswer"]}," for outbound calls, or right after ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["answer"]}," in a webhook response for inbound calls."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The AMD verdict is also delivered as a webhook event: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.human"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.machine"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.beep"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["call.amd.unknown"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prerequisites","__idx":21},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://dashboard.sinch.com"},"children":["Sinch account"]}," with API credentials and a Sinch virtual number."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["For the callback-server path: a publicly reachable URL (use ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://ngrok.com"},"children":["ngrok"]}," during development) and a service set to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WEBHOOK"]},"."]}]}]},"headings":[{"value":"Detect Voicemail & Beeps (AMD)","id":"detect-voicemail--beeps-amd","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"Real-life examples","id":"real-life-examples","depth":2},{"value":"Setup (do this first)","id":"setup-do-this-first","depth":2},{"value":"First success: inline AMD callout (no backend)","id":"first-success-inline-amd-callout-no-backend","depth":2},{"value":"Bash (curl)","id":"bash-curl","depth":3},{"value":"Python","id":"python","depth":3},{"value":"Node.js (native fetch, Node 18+)","id":"nodejs-native-fetch-node-18","depth":3},{"value":"PHP","id":"php","depth":3},{"value":"Java (11+)","id":"java-11","depth":3},{"value":"Browser JavaScript (demo only)","id":"browser-javascript-demo-only","depth":3},{"value":"Place AMD inside an outbound dial (the contract)","id":"place-amd-inside-an-outbound-dial-the-contract","depth":2},{"value":"Dynamic AMD via a callback server","id":"dynamic-amd-via-a-callback-server","depth":2},{"value":"1. Start the server and expose it","id":"1-start-the-server-and-expose-it","depth":3},{"value":"2. Node.js server (Express)","id":"2-nodejs-server-express","depth":3},{"value":"3. Python server (Flask)","id":"3-python-server-flask","depth":3},{"value":"4. See the AMD verdict in your logs","id":"4-see-the-amd-verdict-in-your-logs","depth":3},{"value":"What success looks like","id":"what-success-looks-like","depth":2},{"value":"Tips for AMD accuracy","id":"tips-for-amd-accuracy","depth":2},{"value":"Language coverage","id":"language-coverage","depth":2},{"value":"The amd command at a glance","id":"the-amd-command-at-a-glance","depth":2},{"value":"Prerequisites","id":"prerequisites","depth":2}],"frontmatter":{"seo":{"title":"Detect Voicemail & Beeps (AMD)"}},"lastModified":"2026-08-31T13:48:15.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/voice-2.0/tutorials/amd","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}