Call forwarding
Once a number is active on the platform you can control where its calls go. Two independent rules per number:
forward_all— every inbound call is diverted, to your SIP server (a full SIP address,user@host) or to another phone number.on_missed— only unanswered/failed calls are diverted, to a phone number. ({"type": "voicemail"}is reserved for platform voicemail boxes — coming later; the API refuses it for now.)
Forwarding to a SIP server
{"type": "sip", "sip_endpoint": "+12165455345@pbx.example.com"} delivers
every call as a SIP INVITE sip:+12165455345@pbx.example.com to
pbx.example.com — your PBX, LiveKit SIP endpoint, Asterisk, 3CX, etc. Your
server must accept calls from the carrier's signalling IPs (Telnyx publishes
them); no SIP registration or password is involved. The user part is what
your server sees as the called party — use the number itself (keep the +
if your server matches E.164), or an extension. Telnyx-hosted addresses
(user@yourname.sip.telnyx.com, including another Telnyx account's
subdomain set to receive calls "from anyone") work the same way and are
delivered on-net.
Combine it with on_missed → a phone number to ring your SIP server first
and divert to a mobile when the server is down, busy or does not answer.
How a forwarded call behaves
The platform routes each call as it arrives, from the rules stored on the
number, so rule changes take effect on the very next call. The caller is
not answered by the platform: they hear your target ring and are
connected only when it answers, with their own caller id presented to your
target. If the target does not pick up within 25 s and an on_missed number
is set, the still-ringing caller is sent there; otherwise the call ends as a
normal no-answer. Every step is reported to your webhooks — see
Call notifications.
A bare username (alice, no host) is refused with 422: platform-hosted SIP
accounts are not available yet.
The rules live in the platform's number registry — the source of truth. The platform pushes them to whichever carrier currently hosts your number; you never talk to a carrier, and if a number moves providers the same rules are re-applied automatically.
Set the rules
PUT /v2/numbers/{id}/call_forwarding is a full replace: a rule you omit
is cleared.
curl -X PUT "$API/v2/numbers/$NUMBER_ID/call_forwarding" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"forward_all": {"type": "sip", "sip_endpoint": "14155552671@pbx.example.com"},
"on_missed": {"type": "number", "phone_number": "+447912345678"}
}'
{
"number_id": "66a7fc25-576f-4787-8ff1-92f79c14faa8",
"phone_number": "+14155552671",
"forward_all": {"type": "sip", "sip_endpoint": "14155552671@pbx.example.com"},
"on_missed": {"type": "number", "phone_number": "+447912345678"},
"status": "active",
"updated_at": "2026-09-06T09:00:00Z"
}
Only forward everything to a number? Send just that rule:
{"forward_all": {"type": "number", "phone_number": "+447912345678"}}
Validation to expect:
- Phone targets must be valid E.164; they are normalized for you.
- A number cannot forward to itself (
422). - SIP targets must be a full address (
user@host, e.g.alice@sip.example.comor+12165455345@pbx.example.com); asip:prefix is accepted and stripped. Letters/digits/./_/-in the user part, optionally led by+— keep the+when your server matches the called number in E.164 (most PBX / voice-API integrations do). A bare username is422. - The number must be
activeoractive_unconfigured(409while it is still porting in). - An empty body is a
422— useDELETEto clear forwarding.
Setting rules on an active_unconfigured number flips it to active.
Read and clear
curl "$API/v2/numbers/$NUMBER_ID/call_forwarding" -H "Authorization: Bearer $API_KEY"
curl -X DELETE "$API/v2/numbers/$NUMBER_ID/call_forwarding" -H "Authorization: Bearer $API_KEY"
GET returns both rules (null = not set, calls ring normally). DELETE
removes both rules and tells the carrier to stop forwarding; if nothing else
is configured on the number it returns to active_unconfigured.
Re-sync with the carrier
If calls are not following the rules you see on GET — the carrier side may
have drifted after a failed update, a manual change in a carrier portal, or a
change of hosting provider — push the stored rules again without changing
them:
curl -X POST "$API/v2/numbers/$NUMBER_ID/call_forwarding/sync" \
-H "Authorization: Bearer $API_KEY"
{
"number_id": "66a7fc25-576f-4787-8ff1-92f79c14faa8",
"phone_number": "+14155552671",
"forward_all": {"type": "number", "phone_number": "+447912345678"},
"on_missed": null,
"status": "active",
"updated_at": "2026-09-06T09:00:00Z",
"provider_slug": "telnyx",
"synced": true
}
provider_slug names the carrier that received the rules. A carrier refusal
comes back as 502 with the carrier's own message, so you can see exactly
what it objected to. Nothing is stored and no webhook fires — this is a
read-your-registry, write-the-carrier operation. In the dashboard it is the
Re-sync with carrier button on the forwarding editor.
Call notifications
Every time a call to one of your numbers is forwarded you get a webhook, and a second one when that call ends. Three event types:
| Event | When | Key payload fields |
|---|---|---|
call.forwarded |
the call was sent to a target | forwarded_to, rule (forward_all / on_missed), stage (primary / fallback), from, forwarded_by |
call.completed |
an answered call ended | answered_at, ended_at, duration_seconds, hangup_cause, ended_by |
call.missed |
the call was never answered | reason (no_answer / busy / rejected / caller_hung_up / no_forwarding_rules / failed), hangup_cause |
All three carry the same call_id, number, from, provider and
provider_call_ref, so you can correlate them. forwarded_by tells you
who did the forwarding:
"platform"— the platform routed the call itself (this is how Telnyx-hosted numbers work: the carrier asks us what to do with every call, and we send it to your target without answering the caller first)."carrier"— the hosting carrier forwarded the call natively and reported it to us afterwards. Some carriers we integrate with only work this way; the events look the same, they may just arrive a little later.
A call that rings your forward_all target for 25 s without an answer and
then goes to your on_missed number produces two call.forwarded events
(stage: "primary", then stage: "fallback" with primary_hangup_cause),
followed by one call.completed or call.missed.
{
"type": "call.forwarded",
"payload": {
"call_id": "4b1c9f02-3c7e-4d1a-9b0e-8a2f1c6d7e55",
"number": {"id": "a7e7c447-162a-4f49-998c-add655fd6e61", "phone_number": "+12165455345"},
"from": "+16462427059",
"forwarded_to": {"type": "sip", "sip_endpoint": "+12165455345@kmn-comms.sip.telnyx.com"},
"rule": "forward_all",
"stage": "primary",
"forwarded_by": "platform",
"provider": "telnyx",
"provider_call_ref": "9560e1a6-ad01-11f1-bc48-2221f7f6ad71",
"started_at": "2026-09-11T09:24:26Z",
"occurred_at": "2026-09-11T09:24:26Z"
}
}
Full captured examples of all three (including a carrier-native one) are in
Webhook event examples. The
same calls are browsable at GET /v2/calls (filter by number_id,
phone_number, status) and GET /v2/calls/{id}.
Webhook: rule changes
Every change (set or clear) emits a number.forwarding_updated event to your
webhook endpoints. changed_by is "customer" when you made the change and
"ops" when our support team adjusted the rules for you (always at your
request — they use the same editor and the same rules):
{
"type": "number.forwarding_updated",
"payload": {
"number": {"id": "66a7…", "phone_number": "+14155552671"},
"forwarding": {
"forward_all": {"type": "sip", "sip_endpoint": "alice@pbx.example.com"},
"on_missed": null
},
"changed_by": "customer"
}
}
Configure at port time
You can also pre-set forwarding on the porting order (routing_config on
POST /v2/porting_orders) using the same shape:
{
"phone_numbers": ["+447912345678"],
"routing_config": {
"forward_all": {"type": "sip", "sip_endpoint": "alice@pbx.example.com"}
}
}
When the port completes the number activates as active with those rules
already stored and pushed to the carrier.
Dashboard
On the Numbers page every number has a Forwarding button that opens a dedicated editor page — the same two rules with examples, no JSON required, plus Re-sync with carrier. (For a number still porting in the page explains that forwarding unlocks at activation.)
If a number's rules are correct here but calls still do not follow them
after a re-sync, contact support: the registry may list the wrong hosting
carrier for the number, which our operations team can correct — the
number.provider_changed event tells you when that happens, and your rules
are re-applied to the right carrier.
