Hanzo
Migrate

Twilio

Twilio holds phone numbers and moves messages over them. Three capabilities answer it — /v1/tel for the carrier, /v1/notify for transactional delivery, /v1/channels for chat.

Twilio is three products wearing one account: numbers and the calls and texts over them, transactional delivery through a provider you configure, and chat on somebody else's transport. Each has its own address here — /v1/tel (10 operations), /v1/notify (4), /v1/channels (7).

Nouns

TwilioHanzo
Account SID and auth tokenOne key. Authorization: Bearer $HANZO_API_KEY
POST /2010-04-01/Accounts/{sid}/Messages.jsonPOST /v1/tel/messagesfrom, to, text, media
POST .../Calls.jsonPOST /v1/tel/callsfrom, to, agent, record, webhook
GET .../AvailablePhoneNumbers/{Country}/Local.jsonGET /v1/tel/numbers/available?Country=&Area=&Type=&Limit=
POST .../IncomingPhoneNumbers.jsonPOST /v1/tel/numberse164
Releasing a numberDELETE /v1/tel/numbers/{id}
Message and call logsGET /v1/tel/messages · GET /v1/tel/calls
Hanging up a live callDELETE /v1/tel/calls/{id}
TwiML answering a callagent — the Hanzo assistant that answers it
Twilio Email or SendGridPOST /v1/notify/send/email?sync=true
A transactional SMS through your own Twilio accountPOST /v1/notify/send/sms?sync=true
Conversations on chat transportsPOST /v1/channels/{channel}/senddiscord, slack, teams, telegram

/v1/tel is the carrier: numbers the org bought, and traffic over them. /v1/notify is delivery through the org's own provider credential, read from KMS — naming no provider picks the one that is actually configured (Twilio, then Plivo for SMS; Twilio Email, then SMTP for email) and fails closed when none is. If you are keeping your Twilio account and only moving the code, /v1/notify is the shorter migration.

The call

Twilio:

curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Messages.json" \
  -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
  --data-urlencode "From=+15017250604" \
  --data-urlencode "To=+15558675309" \
  --data-urlencode "Body=Hello from Twilio!"

Hanzo, from a number the org holds:

curl -X POST https://api.hanzo.ai/v1/tel/messages \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "+15017250604",
    "to": "+15558675309",
    "text": "Hello from Hanzo!"
  }'

Buying the number first:

# What the carrier has.
curl -sS "https://api.hanzo.ai/v1/tel/numbers/available?Country=US&Area=415&Limit=5" \
  -H "Authorization: Bearer $HANZO_API_KEY"

# Take one. Provisioning happens with the carrier first and is recorded second,
# so a row here means a number the org really holds.
curl -sS -X POST https://api.hanzo.ai/v1/tel/numbers \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"e164": "+14155550142"}'

Dialling, with an assistant to answer:

curl -X POST https://api.hanzo.ai/v1/tel/calls \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"from": "+15017250604", "to": "+15558675309", "agent": "support", "record": true}'

The call is refused up front when no assistant plane is configured, because a call that connects to silence has already cost the person who answered it.

What does not carry

No TwiML. Twilio hands control of a live call to a markup document — <Say>, <Gather>, <Dial> — fetched from your server. There is no markup here. A call is answered by an agent, and webhook is where events go. An IVR expressed as TwiML has to be re-expressed as an assistant.

from must be a number the org holds. Twilio will send from a Messaging Service, an alphanumeric sender id, or a pool. Here the sender is one of the org's own numbers and nothing else.

No status callbacks per message. The message body is from, to, text and media — there is no StatusCallback field. Delivery state is the status on the row, read back with GET /v1/tel/messages.

No WhatsApp and no RCS. /v1/channels covers Discord, Slack, Teams and Telegram. All four render text today, so attachments and actions in the send envelope may not appear as you expect.

No subaccounts, no Messaging Services, no regions or edges. Tenancy is the org in the key. There is no account tree to mirror and no region/edge to select.

No Verify. Twilio Verify mints a code, delivers it, and checks it back. /v1/notify delivers a message you composed. Identity flows belong to IAM, not to the sender.

sync=true is required on notify. An async dispatch answers 503 — the queue plane that would run it is owned elsewhere. It rides as ?sync=true on the URL, which binds over the body.

How is this guide?

On this page