Skip to content
Patch AMD

Webhooks

Give a webhook_url (HTTPS, public address) when you mint a stream or connect with an API key. When the detection decides, we POST:

{
  "type": "amd.decided",
  "timestamp": "2026-11-10T19:43:03.114Z",
  "data": {
    "detection_id": "det_4f9Qm2LxV7cT1bZr8KpA",
    "result": "voicemail",
    "reason": "recording_detected",
    "at_ms": 2944,
    "heard": "Hi, you've reached Sam, please leave",
    "carrier": "telnyx",
    "call_id": "v3:…",
    "stream_id": "…",
    "metadata": { "lead": "42" }
  }
}

heard is the words heard when the decision was made. It is sent to you and never stored by us.

Delivery

Any 2xx acknowledges. Otherwise we retry after 2 s and 10 s (not on 4xx other than 408 and 429). Each attempt times out after 5 s. Redirects are not followed. Delivery status shows on the detection (webhook_status).

Verify the signature

Webhooks follow Standard Webhooks. Your signing secret (whsec_…) is in the dashboard under Webhooks. Headers:

  • webhook-id — the detection ID
  • webhook-timestamp — Unix seconds
  • webhook-signature — v1, + base64 HMAC-SHA256 of {id}.{timestamp}.{raw body}, keyed with the base64-decoded part of the secret after whsec_
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(req) {
  const id = req.get('webhook-id'), ts = req.get('webhook-timestamp');
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) throw new Error('stale');
  const key = Buffer.from(process.env.PATCH_AMD_WEBHOOK_SECRET.replace(/^whsec_/, ''), 'base64');
  const expected = 'v1,' + createHmac('sha256', key).update(`${id}.${ts}.${req.body}`).digest('base64');
  const ok = req.get('webhook-signature').split(' ').some(sig => sig.length === expected.length && timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));
  if (!ok) throw new Error('bad signature');
  return JSON.parse(req.body);
}

The standardwebhooks packages (Node, Python, Go, Ruby, PHP…) verify these headers directly.