Telnyx
Second carrier for numbers, calls and SMS, next to Twilio.
When both are configured, POST /numbers/quotes compares live inventory,
prices and verification requirements from each and ranks the offers; POST /numbers buys one by quote_id. A number never changes carrier: calls from a
Telnyx number go out through the Telnyx trunk, SMS through Telnyx messaging.
# 1. Live offers for this organization (Twilio + Telnyx)
curl -X POST "$HAIL_API_URL/v1/numbers/quotes" -H "Authorization: Bearer $HAIL_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"country_code":"PT","capabilities":["voice"]}'
# 2. Buy the recommended offer
curl -X POST "$HAIL_API_URL/v1/numbers" -H "Authorization: Bearer $HAIL_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"country_code":"PT","quote_id":"<recommended_quote_id>"}'Or hail numbers acquire --country PT --provider telnyx. Schemas:
openapi/openapi.yaml. Code:
carrier_routing.py,
number_offers.py,
number_orders.py.
Telnyx stays off until all five values below are set. With only
TELNYX_API_KEY set, quotes list Twilio offers only.
.env | Where it comes from |
|---|---|
TELNYX_API_KEY | Mission Control → Account → Keys & Credentials → API Keys |
TELNYX_CONNECTION_ID | The SIP connection from step 2 (numbers Hail buys are attached to it) |
TELNYX_SIP_USERNAME | The SIP connection's username (sent as X-Telnyx-Username on every INVITE) |
LIVEKIT_TELNYX_SIP_OUTBOUND_TRUNK_ID | The LiveKit trunk from step 3 |
TELNYX_PUBLIC_KEY | GET https://api.telnyx.com/v2/public_key → data.public (also under Keys & Credentials → Public Key). Verifies SMS webhooks. |
1. Outbound voice profile
Every SIP connection needs one. Set the countries you will call; the default allows only US and CA.
curl -X POST https://api.telnyx.com/v2/outbound_voice_profiles \
-H "Authorization: Bearer $TELNYX_API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"hail","traffic_type":"conversational","service_plan":"global",
"whitelisted_destinations":["US","CA","GB","PT","SE"],
"daily_spend_limit":"50","daily_spend_limit_enabled":true}'2. SIP connection
An FQDN connection with credentials, as in LiveKit's Telnyx guide. Pick a username (4–32 letters and digits) and a password (8–128 characters).
curl -X POST https://api.telnyx.com/v2/fqdn_connections \
-H "Authorization: Bearer $TELNYX_API_KEY" -H 'Content-Type: application/json' \
-d '{"active":true,"anchorsite_override":"Latency","connection_name":"hail",
"user_name":"<username>","password":"<password>","transport_protocol":"TCP",
"outbound":{"outbound_voice_profile_id":"<profile id from step 1>"}}'The response data.id is TELNYX_CONNECTION_ID; the username is
TELNYX_SIP_USERNAME. The password goes only into the LiveKit trunk below.
3. LiveKit outbound trunk
cat > telnyx-trunk.json <<'EOF'
{"trunk": {"name": "telnyx", "address": "sip.telnyx.com", "numbers": ["*"],
"auth_username": "<username>", "auth_password": "<password>"}}
EOF
lk sip outbound create telnyx-trunk.jsonCopy the trunk ID (ST_…) into .env as LIVEKIT_TELNYX_SIP_OUTBOUND_TRUNK_ID
and restart api. A call from a telnyx number with this value empty fails
with end_reason = carrier_route_failed before any LiveKit room exists.
4. SMS
Nothing to create by hand. When an SMS-capable number becomes active, Hail
creates one Telnyx messaging profile per organization, with its webhook at
<HAIL_API_URL>/sms/telnyx, and attaches the number to it (assigning a texts
agent, or POST /numbers/{id}/enable-sms, runs the same step again). Delivery status
and inbound messages (including STOP/START/HELP) arrive on that route;
Hail checks the Ed25519 signature with TELNYX_PUBLIC_KEY and rejects
requests older than five minutes. HAIL_API_URL must be the public URL Telnyx
can reach.
5. Countries that need verification
Telnyx will not sell some numbers (Sweden, Portugal, Italy, and others)
until a requirement group is approved. Hail files it: GET /v1/verifications/requirements?provider=telnyx&country_code=SE&number_type=mobile
lists the fields and documents Telnyx asks for, POST /v1/verifications
(with provider=telnyx) creates the requirement group under
customer_reference = hail-<organization UUID>, uploads the files, and a
superadmin approval submits it to Telnyx. Once Telnyx approves, quotes for
that country and type turn ready and the order carries the group. The
console does all of this from the number picker. Plug-in:
core/hailhq/core/providers/verification/telnyx.py.
Orders
Telnyx orders complete asynchronously. POST /numbers reserves setup plus the
first month from credits, places the order and returns the number as
pending. A sweeper polls the order every 15 seconds; on success the number
becomes active and the reservation turns into the monthly fee. An order still
pending after two hours, or one Telnyx has no record of, is marked failed and
refunded once. If Telnyx completes it later, release the number in Mission
Control by hand; the API log names the order.
Inbound calls
Telnyx sends a call to LiveKit through the FQDN connection
(TELNYX_CONNECTION_ID) when that connection has inbound enabled and its
FQDN is LiveKit's SIP endpoint. Hail sets the number's voice connection when
it routes calls to an agent; you set up the connection once.
- On the connection, Inbound: Destination number format
+E.164, Origination number format+E.164, transport TCP. - Add an FQDN:
<project>.sip.livekit.cloud(port 5060, no username). - Create the LiveKit inbound trunk and dispatch rule
(LiveKit Cloud §4) and set
LIVEKIT_SIP_INBOUND_TRUNK_ID.
PATCH /v1/numbers/{id} with voice_agent_id then sets
connection_id on the number (PATCH /v2/phone_numbers/{id}/voice) and
lists it on the LiveKit trunk; null clears both (Agents).
Reference: LiveKit Telnyx guide.