Skip to content
Patch AMD

Twilio

Twilio runs the TwiML for an outbound call when the callee answers. Return <Start><Stream> from that webhook to fork the callee's audio to Patch AMD while the call continues, then act on the amd.decided webhook.

Twilio's <Stream url> accepts neither headers nor a query string, so authenticate with a <Parameter>: mint a signed stream token per call and pass it as token.

1. Place the call

Turn off Twilio's own AMD (machineDetection) — Patch AMD replaces it.

await twilio.calls.create({
  to: '+15551234567',
  from: '+15557654321',
  url: 'https://you.example/twilio/answered?lead=42',
});

2. Answer webhook: start the stream

app.post('/twilio/answered', async (req, res) => {
  const stream = await fetch('https://api.patchamd.com/v1/streams', {
    method: 'POST',
    headers: { authorization: `Bearer ${process.env.PATCH_AMD_KEY}`, 'content-type': 'application/json' },
    body: JSON.stringify({ webhook_url: 'https://you.example/amd', metadata: { lead: req.query.lead } }),
  }).then(r => r.json());

  res.type('text/xml').send(`<Response>
  <Start>
    <Stream url="${stream.base_url}" track="inbound_track">
      <Parameter name="token" value="${stream.token}"/>
    </Stream>
  </Start>
  <Pause length="20"/>
</Response>`);
});

track="inbound_track" sends only the callee's side. The <Pause> keeps the call open while we listen; the decision arrives long before it ends.

3. Route the call on the decision

The webhook's data.call_id is the Twilio CallSid.

app.post('/amd', express.raw({ type: 'application/json' }), async (req, res) => {
  const event = verify(req); // see /docs/webhooks
  res.sendStatus(204);
  const { result, call_id } = event.data;
  if (result === 'human' || result === 'not_sure') {
    await twilio.calls(call_id).update({ twiml: '<Response><Dial><Client>rep</Client></Dial></Response>' });
  } else {
    await twilio.calls(call_id).update({ status: 'completed' });
  }
});

Alternatives

  • No REST call per dial: put your API key in the TwiML instead of a token: <Parameter name="api_key" value="pamd_live_…"/> plus <Parameter name="webhook_url" value="https://you.example/amd"/>. Other <Parameter> values are echoed back as metadata. This exposes the key to Twilio's request logs; tokens are preferred.
  • Decision on the socket: if you relay the media yourself, add <Parameter name="events" value="1"/> and read events from the socket (Twilio itself ignores them).

Audio

Twilio Media Streams send 8 kHz mu-law (audio/x-mulaw) in 20 ms frames, which we take as-is.