The call waterfall

Read why a call went where it went: every check it passed, every target considered and why each was skipped, every ping and every dial attempt, in words — and the redacted version your publisher sees.

After this page you can open any call and say, in one sentence, why it ended up where it did — and you will know exactly what your publisher is shown about the same call.

"Why did this call not route?" is the first question anybody asks of a call tracking platform, and the usual answer is a code only the vendor can read. Here the call answers for itself. The waterfall is the record of every decision the router made about a call, written as the call happens — each check, the routing plan with every target that was passed over, each ping to a buyer, each dial and how it ended. It is never rebuilt afterwards from settings that may have changed since. If you renamed a target or raised a cap an hour later, the waterfall still says what was true at the time.

Where to read it

ReaderWhereWhat they get
YouCalls in the console: open a call. Or GET /api/v1/calls/:id with a key that holds calls:read.The whole story: names, masked destinations, bids, revenue, payout and profit.
Your publisherTheir portal's call page, for calls they sent. They need the view calls permission on their login.A story built for them: what happened to their call and whether it paid. No buyer, no target, no destination, no bid, no SIP code, no revenue.
curl "https://api.buy3.io/api/v1/calls/0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11" \
  -H "Authorization: Bearer $BUY3_API_KEY"

The shape of a story

The answer has two halves: call, the facts of the call as the call log lists them plus the frozen terms and recordings, and story, the explanation.

GET /api/v1/calls/:id (outline)
{
  "call": { "id": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11", "ref": "CA-482137", "outcome": "converted" },
  "story": {
    "summary": { "outcome": "converted", "sentence": "Connected to BlueSky Legal — RTB (BlueSky Legal) on the second attempt and talked for 3 minutes 4 seconds. It converted for $38.00, with $27.50 owed to the publisher." },
    "steps": [],
    "attempts": [],
    "pings": [],
    "money": {},
    "events": []
  },
  "recordingsReason": null
}
KeyHolds
summaryThe call's outcome and one sentence that tells the whole story. If you read nothing else, read this.
stepsThe waterfall itself, in the order things happened. Every step has at, step, title, sentence, tone and a detail object for programs.
attemptsOne entry per dial attempt, as a table: who was rung, for how long, and the result in words.
pingsEvery bid request the router sent to a buyer's bidding endpoint for this call, with status, bid and latency.
moneyThe call's books, the frozen terms they were written on, and any manual adjustments.
eventsThe carrier-side event trail as an envelope only: id, at, source, type. Payloads are not served.

tone is one of info, success, warning and danger. A warning is the plan working as designed — a target was skipped, an attempt rang out, the call moved on. A danger is a call that stopped.

A call, step by step

The rest of this page follows one real-shaped call through the router. A caller in California dials a number that your publisher Northwind Media runs. The campaign, Medicare — Inbound, routes in priority order across four targets: two ordinary phone targets and two external RTB targets that are asked to bid. The first buyer does not pick up; the second does; the call lasts just over three minutes and converts.

1. The call arrives

step: incoming
{
  "at": "2026-09-20T14:03:11.000Z",
  "step": "incoming",
  "title": "Call received",
  "tone": "info",
  "sentence": "A call from +14155550142 (CA) arrived on +18885550100 for the campaign Medicare — Inbound, from the publisher Northwind Media. It carried 4 tags.",
  "detail": {
    "callerNumber": "+14155550142",
    "callerState": "CA",
    "dialedNumber": "+18885550100",
    "numberId": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
    "publisherId": "c4e1a2b3-9d8f-4f6e-b5a4-1c2d3e4f5a6b",
    "poolId": null,
    "sessionId": null,
    "tags": {
      "dialed_number": "+18885550100",
      "publisher": "Northwind Media",
      "caller_area_code": "415",
      "caller_state": "CA"
    },
    "tagSources": {
      "dialed_number": "system",
      "publisher": "system",
      "caller_area_code": "system",
      "caller_state": "system"
    }
  }
}
  • The state in brackets comes from the caller's area code. It is a hint about where the number was issued, not proof of where the caller is.
  • tagSources says where each tag came from: publisher, dni, number, system, api or user. A dialled call always carries the four system tags shown here. Had a website visitor's session been matched, the sentence would say so and sessionId would be set.
  • When the number has no publisher the sentence ends "on the workspace's own media". A caller who hid their number reads "a hidden number".

2. The checks

step: gates
{
  "at": "2026-09-20T14:03:11.000Z",
  "step": "gates",
  "title": "Checks passed",
  "tone": "success",
  "sentence": "The call passed every check before routing: the subscription is active, the wallet has funds, the campaign is live, the caller is not blocked, the caller ID is acceptable, the campaign is open, the campaign is under its caps and the repeat-caller policy allows this caller.",
  "detail": {
    "passed": [
      "workspace_inactive", "workspace_unfunded", "campaign_not_live", "caller_blocked",
      "anonymous_blocked", "campaign_closed", "campaign_capped", "repeat_blocked"
    ],
    "failed": null
  }
}

Eight checks run before any buyer is considered, always in this order: subscription, wallet, campaign status, blocked caller, anonymous caller, campaign hours, campaign caps, repeat caller. Each one is named in passed by the reason code it would have failed with — so workspace_unfunded in the list means the wallet check passed. The first check that fails ends the call, becomes failed, and is the call's unrouted reason; see When a call is not routed.

3. The pings

step: ping
{
  "at": "2026-09-20T14:03:12.540Z",
  "step": "ping",
  "title": "Asked buyers to bid",
  "tone": "info",
  "sentence": "Asked 2 bidding endpoints for a price (1,512 ms): 1 bid and 1 timed out. The best bid was $38.00 from BlueSky Legal — RTB.",
  "detail": {
    "elapsedMs": 1512,
    "targets": [
      { "targetId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "targetName": "BlueSky Legal — RTB", "status": "bid", "bidCents": 3800, "latencyMs": 412, "httpStatus": 200 },
      { "targetId": "d3e4f5a6-b7c8-4d9e-8f0a-2b3c4d5e6f7a", "targetName": "Summit Care — RTB", "status": "timeout", "bidCents": null, "latencyMs": 1500, "httpStatus": null }
    ]
  }
}

This step appears only when the plan holds external RTB targets. They are asked for a price in parallel, under one time budget, before the plan is drawn — a bid is what gives such a target its price and usually its destination. Here BlueSky Legal bid and Summit Care's endpoint did not answer in time. Every one of these requests is also a row in the outbound ping ledger, with the request and the response.

4. The routing plan, and who was skipped

step: plan
{
  "at": "2026-09-20T14:03:12.545Z",
  "step": "plan",
  "title": "Routing plan",
  "tone": "info",
  "sentence": "4 targets were considered, in priority order: 2 could take the call and 2 were skipped. First in line: Acme Health — Dallas floor (Acme Health).",
  "detail": {
    "mode": "priority",
    "eligible": [
      { "targetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d", "targetName": "Acme Health — Dallas floor", "buyerId": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b", "buyerName": "Acme Health", "priority": 1, "weight": 1, "revenueCents": 4200, "source": "static" },
      { "targetId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "targetName": "BlueSky Legal — RTB", "buyerId": "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a", "buyerName": "BlueSky Legal", "priority": 2, "weight": 1, "revenueCents": 3800, "source": "rtb" }
    ],
    "skipped": [
      { "targetId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "targetName": "Harbor Mutual — Direct", "buyerId": "3e4f5a6b-7c8d-4e9f-8a1b-2c3d4e5f6a7b", "buyerName": "Harbor Mutual", "reason": "capped", "sentence": "The target Harbor Mutual — Direct has reached its daily cap of 50 calls." },
      { "targetId": "d3e4f5a6-b7c8-4d9e-8f0a-2b3c4d5e6f7a", "targetName": "Summit Care — RTB", "buyerId": "4f5a6b7c-8d9e-4f0a-9b2c-3d4e5f6a7b8c", "buyerName": "Summit Care", "reason": "timeout", "sentence": "The bidding endpoint of the buyer Summit Care did not answer within 1,500 ms." }
    ]
  }
}

eligible is the dialling order the router will actually follow, already sorted by the campaign's routing mode. source is static for a target with its own destination and rtb for one that bid. skipped lists every target that was passed over, each with a reason code and a sentence that names names: not "a cap was reached" but which target, which window and which figure.

Because "why did Harbor Mutual not get this call?" is asked about a target by name, each skipped target also gets a step of its own, straight after the plan:

step: skipped (one per skipped target)
{
  "at": "2026-09-20T14:03:12.545Z",
  "step": "skipped",
  "title": "Harbor Mutual — Direct skipped",
  "tone": "warning",
  "sentence": "The target Harbor Mutual — Direct has reached its daily cap of 50 calls.",
  "detail": {
    "targetId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
    "targetName": "Harbor Mutual — Direct",
    "buyerId": "3e4f5a6b-7c8d-4e9f-8a1b-2c3d4e5f6a7b",
    "buyerName": "Harbor Mutual",
    "reason": "capped"
  }
}

5. The first dial, and failover

step: dial (attempt 1)
{
  "at": "2026-09-20T14:03:13.000Z",
  "step": "dial",
  "title": "Attempt 1 · Acme Health — Dallas floor",
  "tone": "warning",
  "sentence": "Rang Acme Health — Dallas floor (Acme Health) on the number ending 0175 — no answer after 18 seconds (SIP 480, the line was unavailable).",
  "detail": {
    "attemptNo": 1,
    "outcome": "no_answer",
    "rangSeconds": 18,
    "endedAt": "2026-09-20T14:03:31.000Z",
    "targetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
    "targetName": "Acme Health — Dallas floor",
    "buyerId": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
    "buyerName": "Acme Health",
    "deliveryMode": "pstn_did",
    "destination": "the number ending 0175",
    "sipCode": 480,
    "sipText": "the line was unavailable"
  }
}
  • A dial and its result are told as one step. While an attempt is still ringing the sentence reads "Ringing …" and outcome is null.
  • destination is masked, always: the last four digits of a number, or the host of a SIP address. A screenshot of this page never carries a buyer's direct line.
  • The carrier's answer is given in words and as a code, because a busy line and an unavailable one are the same missed call to you and two different faults to whoever runs the buyer's phone system. See SIP codes.
  • Before it rings a target, the router reserves a line on the target, on its buyer and on its endpoint. A failed attempt gives all three back before the next one starts.
step: failover
{
  "at": "2026-09-20T14:03:31.000Z",
  "step": "failover",
  "title": "Failover",
  "tone": "info",
  "sentence": "Moving on to BlueSky Legal — RTB.",
  "detail": null
}

Failover carries on down eligible until somebody answers or the campaign's failover budget runs out. If every eligible target fails, the call ends all_targets_failed and the campaign's unrouted action takes over.

6. The second dial connects

steps: dial (attempt 2) and connected
[
  {
    "at": "2026-09-20T14:03:32.000Z",
    "step": "dial",
    "title": "Attempt 2 · BlueSky Legal — RTB",
    "tone": "success",
    "sentence": "Rang BlueSky Legal — RTB (BlueSky Legal) on the number ending 0188 — answered.",
    "detail": {
      "attemptNo": 2, "outcome": "answered", "rangSeconds": 6, "endedAt": "2026-09-20T14:03:38.000Z",
      "targetId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "targetName": "BlueSky Legal — RTB",
      "buyerId": "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a", "buyerName": "BlueSky Legal",
      "deliveryMode": "pstn_did", "destination": "the number ending 0188", "sipCode": null, "sipText": null
    }
  },
  {
    "at": "2026-09-20T14:03:38.000Z",
    "step": "connected",
    "title": "Connected",
    "tone": "success",
    "sentence": "BlueSky Legal — RTB answered on the second attempt, 27 seconds after the call arrived.",
    "detail": { "attemptNo": 2, "targetId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "targetName": "BlueSky Legal — RTB", "secondsToConnect": 27 }
  }
]

Connected means the buyer's leg answered. The platform answers the caller's own leg earlier, so that there is something to transfer, and that never counts as a connection. secondsToConnect is measured from the moment the call arrived, so it includes the eighteen seconds lost on the first target.

7. How it ended, and what it was worth

steps: ended and settled
[
  {
    "at": "2026-09-20T14:06:42.000Z",
    "step": "ended",
    "title": "Call ended",
    "tone": "info",
    "sentence": "The call ended after 3 minutes 4 seconds of talk time — the caller hung up.",
    "detail": { "talkSeconds": 184, "hungUpBy": "caller", "hasRecording": true }
  },
  {
    "at": "2026-09-20T14:06:43.000Z",
    "step": "settled",
    "title": "Converted",
    "tone": "success",
    "sentence": "The call converted: $38.00 in revenue from BlueSky Legal, $27.50 owed to the publisher Northwind Media — $10.50 profit.",
    "detail": {
      "converted": true, "revenueCents": 3800, "payoutCents": 2750, "profitCents": 1050,
      "durationThresholdSeconds": 90, "conversionSource": "duration", "corrected": false
    }
  }
]
  • hungUpBy is caller, agent (the far end — on a tracking call, the buyer) or platform.
  • The call converted because it lasted 184 seconds and BlueSky Legal's bid converts after 90. Revenue is the price frozen on the attempt that answered, not the first target's price.
  • The payout follows the publisher's own payout terms, frozen when the call arrived. A publisher can be owed money on a call that did not convert for the buyer, and the sentence says so when that happens.
  • A call that did not convert explains why: "it lasted 45 seconds and BlueSky Legal pays after 1 minute 30 seconds. No revenue was booked."
  • If the figures were later changed by hand, the sentence ends "These figures were corrected by hand." and corrected is true. See Conversions and adjustments.

The attempts table

attempts is the same two dials as a table, for a screen that wants rows rather than prose. result is the sentence fragment used in the step.

story.attempts
[
  {
    "attemptNo": 1, "startedAt": "2026-09-20T14:03:13.000Z", "endedAt": "2026-09-20T14:03:31.000Z",
    "rangSeconds": 18, "outcome": "no_answer",
    "result": "no answer after 18 seconds (SIP 480, the line was unavailable)", "tone": "warning",
    "targetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d", "targetName": "Acme Health — Dallas floor",
    "buyerId": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b", "buyerName": "Acme Health",
    "agentName": null, "deliveryMode": "pstn_did", "destination": "the number ending 0175",
    "sipCode": 480, "sipText": "the line was unavailable", "lossCode": null, "whisperAccepted": null
  },
  {
    "attemptNo": 2, "startedAt": "2026-09-20T14:03:32.000Z", "endedAt": "2026-09-20T14:03:38.000Z",
    "rangSeconds": 6, "outcome": "answered", "result": "answered", "tone": "success",
    "targetId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "targetName": "BlueSky Legal — RTB",
    "buyerId": "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a", "buyerName": "BlueSky Legal",
    "agentName": null, "deliveryMode": "pstn_did", "destination": "the number ending 0188",
    "sipCode": null, "sipText": null, "lossCode": null, "whisperAccepted": null
  }
]

When a call is not routed

A call that stops at a check has a short waterfall. The gate says why, and the unrouted step says what happened to the caller — it does not repeat the reason one line later.

A call outside the campaign's hours
[
  {
    "at": "2026-09-20T02:11:40.000Z",
    "step": "gates",
    "title": "Stopped before routing",
    "tone": "danger",
    "sentence": "The call arrived outside the campaign's hours of operation.",
    "detail": {
      "passed": ["workspace_inactive", "workspace_unfunded", "campaign_not_live", "caller_blocked", "anonymous_blocked"],
      "failed": { "code": "campaign_closed", "sentence": "The call arrived outside the campaign's hours of operation." }
    }
  },
  {
    "at": "2026-09-20T02:11:40.000Z",
    "step": "unrouted",
    "title": "Not routed",
    "tone": "danger",
    "sentence": "The caller heard the campaign's message and was then disconnected.",
    "detail": { "code": "campaign_closed", "action": "message" }
  }
]
actionWhat the caller experienced
hangupThe caller was disconnected.
messageThe caller heard the campaign's message and was then disconnected.
forwardThe caller was forwarded to the campaign's fallback number. That dial appears as a step titled Forwarded, and an answer there does not make the call connected.
Only five reasons earn the campaign's message or fallback number: campaign_closed, campaign_capped, no_targets, no_eligible_target and all_targets_failed. A blocked caller, a lapsed subscription and an empty wallet are always a hang-up.

When the checks all pass and the plan has nobody to offer, the last step reads no_targets (the plan has no active route) or no_eligible_target (every target was skipped — the steps above it say why, one by one).

A call won at auction

A publisher can also reach a campaign by pinging it, winning the auction and claiming the bid. That call gets the same waterfall, with the auction standing in for the two steps the router did not run:

  • The gates step is titled Won at auction, and its detail carries "skipped": true. The campaign's checks ran when the publisher pinged, not when the caller arrived.
  • The ping step lists the external buyers the auction asked and what each said.
  • The plan puts the claimed bid first and the auction's runners-up after it, in the auction's own rank order.
  • If the caller never arrives, the waterfall says that the publisher claimed the call and never sent it.

The same call, as your publisher sees it

Northwind Media sent this call and can open it in their portal. Their story is built, not filtered: each step is assembled from fixed wording and from values named one at a time, so a field added to your story tomorrow cannot leak into theirs. A sentence the router stored for you, which names your buyer on purpose, is never reused for them.

story, publisher audience
{
  "summary": {
    "outcome": "converted",
    "sentence": "Connected to a buyer on the second attempt and talked for 3 minutes 4 seconds. This call paid $27.50."
  },
  "steps": [
    { "at": "2026-09-20T14:03:11.000Z", "step": "incoming", "title": "Call received", "tone": "info",
      "sentence": "Your call from ••••••0142 arrived on +18885550100 for the campaign Medicare — Inbound.",
      "detail": { "callerNumber": "••••••0142", "dialedNumber": "+18885550100" } },
    { "at": "2026-09-20T14:03:11.000Z", "step": "gates", "title": "Accepted", "tone": "success",
      "sentence": "The campaign accepted the call.", "detail": null },
    { "at": "2026-09-20T14:03:12.545Z", "step": "plan", "title": "Buyer available", "tone": "info",
      "sentence": "A buyer was available to take this call.", "detail": null },
    { "at": "2026-09-20T14:03:13.000Z", "step": "dial", "title": "Attempt 1", "tone": "warning",
      "sentence": "Rang a buyer — no answer after 18 seconds.",
      "detail": { "attemptNo": 1, "outcome": "no_answer", "rangSeconds": 18, "endedAt": "2026-09-20T14:03:31.000Z" } },
    { "at": "2026-09-20T14:03:32.000Z", "step": "dial", "title": "Attempt 2", "tone": "success",
      "sentence": "Rang a buyer — answered.",
      "detail": { "attemptNo": 2, "outcome": "answered", "rangSeconds": 6, "endedAt": "2026-09-20T14:03:38.000Z" } },
    { "at": "2026-09-20T14:03:38.000Z", "step": "connected", "title": "Connected", "tone": "success",
      "sentence": "A buyer answered on the second attempt, 27 seconds after the call arrived.",
      "detail": { "attemptNo": 2, "secondsToConnect": 27 } },
    { "at": "2026-09-20T14:06:42.000Z", "step": "ended", "title": "Call ended", "tone": "info",
      "sentence": "The call ended after 3 minutes 4 seconds of talk time — the caller hung up.",
      "detail": { "talkSeconds": 184, "hungUpBy": "caller" } },
    { "at": "2026-09-20T14:06:43.000Z", "step": "settled", "title": "Paid", "tone": "success",
      "sentence": "This call paid $27.50.",
      "detail": { "paid": true, "payoutCents": 2750 } }
  ],
  "attempts": [
    { "attemptNo": 1, "startedAt": "2026-09-20T14:03:13.000Z", "endedAt": "2026-09-20T14:03:31.000Z", "rangSeconds": 18, "outcome": "no_answer", "result": "no answer after 18 seconds", "tone": "warning" },
    { "attemptNo": 2, "startedAt": "2026-09-20T14:03:32.000Z", "endedAt": "2026-09-20T14:03:38.000Z", "rangSeconds": 6, "outcome": "answered", "result": "answered", "tone": "success" }
  ],
  "pings": [],
  "money": { "settled": true, "settledAt": "2026-09-20T14:06:43.000Z", "paid": true, "payoutCents": 2750 },
  "events": []
}
In your storyIn the publisher's
The caller's full numberMasked to the last four digits, unless the campaign discloses caller IDs to publishers.
The eight checks, by nameAccepted — or Bid accepted for a call they won at auction. detail is null.
The ping step and every bidAbsent. pings is always [].
The plan, its prices and each skipped targetBuyer available or No buyer available. No skipped steps, no failover step.
"Rang BlueSky Legal — RTB (BlueSky Legal) on the number ending 0188""Rang a buyer". No name, no destination, no SIP code.
Revenue, payout and profitPaid or Not paid, and the payout only.
The carrier event trailevents is always [].

An unrouted call is reworded for them too. They are never told that your wallet was empty or your subscription had lapsed: workspace_inactive, workspace_unfunded and campaign_not_live all read "The campaign was not accepting calls when this call arrived." The full table is in Reason codes. And when a call did not pay, they are told why in terms that are theirs to know: "This call did not pay: it lasted 45 seconds and your lane pays after 1 minute."

Beside the story, the publisher's call object holds their own side only: the masked caller, durationSeconds, billable, payoutCents, requiredSeconds — the duration this call had to reach to pay, as frozen on the call — and only the tags they supplied themselves. A tag your landing page or your number added is not theirs to read.

Next steps