number.status_changed

View as Markdown

Emitted every time one of your Dial numbers’ capabilities moves. Provisioning a number returns as soon as the number itself is held, which can be before every channel on it works; this event tells you the moment each one becomes usable — so you can react instead of polling GET /api/v1/numbers/{id} in a loop.

Capabilities settle separately

A number is not “provisioning” as a whole. Each capability has its own clock:

CapabilitySettles
smsimmediately — usable as soon as the number exists
imessageimmediately (iMessage numbers only)
callimmediately on a standard number; asynchronously on an iMessage number, which must verify its caller ID before it can dial
whatsappasynchronously, on its own registration (only when the WhatsApp opt-in was taken)

So two capabilities can be in flight, and they can disagree: a number whose WhatsApp was declined still sends SMS, places calls and messages over iMessage. That is why every capability reports its own status, and why errors live only on the capability that owns them — there is no number-level error field.

The number’s own status

status folds the capabilities into one value:

statusMeaning
unsettledat least one capability is still working on it
readyevery capability is ready
degradednothing is still in flight, and at least one capability failed

ready and degraded are both terminal, so waiting for “either” always resolves. Wait for only ready and you can wait forever: WhatsApp can decline a number outright, and no retry will change that — the number is finished setting up, just not perfectly.

Anything still in flight outranks a failure. A number with a failed WhatsApp track and a caller-ID check still running is unsettled, not degraded: it has not finished trying, and a terminal status would invite you to stop waiting.

Schema

Shares the common event envelope (id, object, type, version, createdAt, relatedObject). relatedObject is { id: <phoneNumberId>, type: "phone_number", url: "/api/v1/numbers/<phoneNumberId>" }. The envelope’s createdAt is when the transition happened.

This event’s payload is at version: 2. Every other event is at version: 1 — the version is per event type, not global, so branch on (type, version) rather than on version alone. Version 1 of this payload was flat (setupStatus, setupError, and capabilities as a string array); it could report only the line’s own setup and had nowhere to put a WhatsApp failure or its cooldown.

data fieldTypeDescription
phoneNumberIdstringDial-issued ID for the number. Matches the id from GET /api/v1/numbers.
numberstringThe phone number in E.164, e.g. +14155550123.
statusstring"unsettled", "ready" or "degraded" — the fold over capabilities, described above.
previousStatusstring | nullThe settlement status the number left. null on the number’s first event.
changedarrayThe capability names that moved in this transition. Empty on a number’s first event, where nothing moved. Usually one name; two when a WhatsApp track settling is what released the line’s call capability, since both move in the same instant.
capabilitiesobjectCapability name → its state. A capability the number does not have is absent, which is different from one that is present and not ready.

Each entry in capabilities:

FieldTypeDescription
statusstring"provisioning", "ready" or "failed".
previousStatusstring | nullThe status this capability left, set when it is one of the changed ones. Absent on a synchronous capability, which never transitions.
errorstring | nullHuman-readable reason, non-null only when status is "failed". Safe to show a user. Absent on a synchronous capability, which cannot fail.
retryAvailableAtstring | nullISO-8601 timestamp — the cooldown. When non-null, this capability refuses another attempt until then. null means you can retry now, or that no retry will help (error says which). Absent on a synchronous capability.

Examples

An iMessage number finishing its caller-ID verification while WhatsApp is still going:

1{
2 "id": "evt_9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c",
3 "object": "event",
4 "type": "number.status_changed",
5 "version": 2,
6 "createdAt": "2026-09-03T10:15:22.481Z",
7 "relatedObject": { "id": "clx8k2j9h0001mk08w3t5r7qz", "type": "phone_number", "url": "/api/v1/numbers/clx8k2j9h0001mk08w3t5r7qz" },
8 "data": {
9 "phoneNumberId": "clx8k2j9h0001mk08w3t5r7qz",
10 "number": "+14155550123",
11 "status": "unsettled",
12 "previousStatus": "unsettled",
13 "changed": ["call"],
14 "capabilities": {
15 "sms": { "status": "ready" },
16 "imessage": { "status": "ready" },
17 "call": { "status": "ready", "previousStatus": "provisioning", "error": null, "retryAvailableAt": null },
18 "whatsapp": { "status": "provisioning", "previousStatus": null, "error": null, "retryAvailableAt": null }
19 }
20 }
21}

A standard number, whose whole lifecycle is one event:

1"data": {
2 "phoneNumberId": "clx0a1b2c3004mk08q9r8s7t6",
3 "number": "+14155559876",
4 "status": "ready",
5 "previousStatus": null,
6 "changed": [],
7 "capabilities": {
8 "sms": { "status": "ready" },
9 "call": { "status": "ready" }
10 }
11}

WhatsApp failing with a cooldown — the number keeps working:

1"data": {
2 "phoneNumberId": "clx8k2j9h0001mk08w3t5r7qz",
3 "number": "+14155550123",
4 "status": "degraded",
5 "previousStatus": "unsettled",
6 "changed": ["whatsapp"],
7 "capabilities": {
8 "sms": { "status": "ready" },
9 "imessage": { "status": "ready" },
10 "call": { "status": "ready", "previousStatus": null, "error": null, "retryAvailableAt": null },
11 "whatsapp": {
12 "status": "failed",
13 "previousStatus": "provisioning",
14 "error": "WhatsApp stopped accepting verification requests for this number. Try again in a couple of hours.",
15 "retryAvailableAt": "2026-09-03T12:15:22.481Z"
16 }
17 }
18}

Waiting for a number to finish setting up:

$dial wait-for number.status_changed -f status=ready -f phoneNumberId=clx8k2j9h0001mk08w3t5r7qz

Filters match top-level data fields, so status, previousStatus, number and phoneNumberId are filterable; capabilities is nested, so read it from the payload rather than filtering on it.

When it fires

  • Once per observable change. A number’s setup involves several internal steps that map to the same capability status, and those are not published — you get an event when a capability’s status actually changes, not once per internal write.
  • Every number gets a first event, previousStatus: null and changed: [], published as it is created — so a consumer waits on the same event whichever kind of number it provisioned, and never has to know which kind that was. A standard number is ready there and emits nothing further. On the presence-based stream that first event can be published before the provisioning HTTP response reaches you, so a listener that subscribes afterwards may miss it; the response already told you the status. Webhook delivery is queued and retried, so a webhook consumer always receives it.
  • Retrying a failed capability emits a fresh event, moving the number back to unsettled, followed by another when it resolves. Each cycle carries its own event ids; nothing is collapsed into the earlier attempt. Deduplicate on the envelope id.
  • changed can name two capabilities. A WhatsApp track reaching a terminal state is what releases a caller-ID-verified line from waiting on it, so whatsapp and call settle together and are reported in one event rather than two half-truths.
  • Releasing a number emits nothing — the number stops existing, and there is no status for that.