Conversions and adjustments

How a call converts on its own, how to mark or post a conversion, how to tag a call after the fact, and how to correct its books without losing the record of why.

After this page you can say why any call did or did not convert, change that when your buyer tells you something the phone system could not know, and correct a figure in a way that survives everything the platform does afterwards. A call's money is settled when the call ends, and most of the time that needs nothing from you.

None of this moves any money. A tracking call's revenue and payout are books-only — the record of what your buyer owes you and what you owe your publisher, both settled off the platform. Correcting them changes your reports and your statements, and no wallet anywhere.

How a call converts on its own

When a call ends, the platform writes its verdict from values frozen on the call when it was routed — never from what the target says now. A buyer repriced at three o'clock has not repriced the call that started at 2:58.

SideConverts whenThe terms come from
Revenue — what the buyer owes youA buyer answered and talked for at least the frozen duration threshold. Or a conversion was posted for the call.The target that took the call, or the routing plan's override for it, frozen on the dial attempt.
Payout — what you owe the publisherThe publisher's own terms are met: their own buffer if their lane sets one, otherwise the buyer's threshold.The publisher's lane, frozen when the call started.
Talk time is measured from the moment the buyer answered, never from when the platform picked the caller up.

The two sides are different deals, and they can disagree. A lane that pays on 60 seconds owes nothing for a 45-second call even though the buyer's 30-second threshold converted it — and owes its fixed payout for a 70-second call the buyer's 90-second threshold did not. That is the whole business, working.

The books say reasonMeaning
durationTalk time met the frozen threshold. The usual conversion.
postbackThe buyer reported it.
manualSomebody said so by hand.
not_answeredNo buyer ever answered.
no_buyerThe call reached no target at all. An unrouted call that the fallback number answered lands here, however long the conversation was.
under_threshold:41s_of_90sA buyer answered and hung up short. The two figures are the talk time and the threshold it had to beat.
It is on call.completed and call.converted — see Events — and in the settled step of the call's waterfall.

Three things you can do afterwards

ConversionAdjustmentTags
AnswersDid this call earn its price?What were the figures, exactly?What do we know about it now?
Changesconverted, and the revenue with it.revenueCents, payoutCents, or both.The call's tags.
NeedsA call that has ended.A call that has ended.Nothing — any call.
Written to the activity logYes, medium severity.Yes, medium severity.Yes, low severity.
Fires an eventcall.converted, when it newly converts.call.converted, when it newly converts.None.

Conversions

Some buyers only pay for a call that became a sale, and only they know which did. A conversion records that against the call, and it can arrive three ways.

WhoHowNotes
You, in the consoleThe call drawer's Mark this call converted.Shows Booked now, Payout now and Talk time against the threshold before you decide.
Your systemPOST /api/v1/calls/:id/conversion, with a key that has calls:write.Takes the same fields as the dialog.
The buyerThe per-call postback URL and key they were handed with the call.Within 72 hours of the call ending, and once — see below.
curl -X POST "https://api.buy3.io/api/v1/calls/0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11/conversion" \
  -H "Authorization: Bearer $BUY3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "converted": true,
    "revenueCents": 5000,
    "note": "Buyer confirmed the transfer by email"
  }'
200 OK
{
  "call": {
    "ref": "CA-482137",
    "converted": true,
    "revenueCents": 5000,
    "payoutCents": 2750,
    "profitCents": 2250,
    "convertedAt": "2026-09-20T16:22:04.118Z",
    "conversionSource": "manual",
    "conversionNote": "Buyer confirmed the transfer by email",
    "frozen": {
      "revenueCents": 4200,
      "payoutCents": 0,
      "durationThresholdSeconds": 90
    },
    "corrections": { "converted": true, "revenueCents": 5000 },
    "adjustments": []
  },
  "before": { "converted": false, "revenueCents": 0, "payoutCents": 0, "convertedAt": null },
  "after": { "converted": true, "revenueCents": 5000, "payoutCents": 2750, "convertedAt": "2026-09-20T16:22:04.118Z" }
}
  • converted is required, and false is how a converted call stops being one. The engine never revokes on its own; only a person, saying so, on an audited route.
  • revenueCents is optional. Leave it out and the call books the price frozen on it; send it to override that for this call only. converted: false with a revenue above zero is refused — a call that did not convert has no revenue.
  • note is kept on the call and repeated in the activity log. Write the reason, not the action: "Buyer confirmed the transfer by email" outlives "marked converted".
  • The payout is recomputed from the publisher's frozen terms against the new revenue, so a revenue-share lane follows the correction without a second request.
  • before and after come back in the answer, which is what you log on your side.

When the buyer posts it themselves

RuleDetail
Window72 hours after the call ends. Later answers 410 window_closed.
OnceA second postback answers 409 already_converted with the time of the first. A correction goes through the console or the API instead of silently overwriting it.
A negative postbackA buyer saying the call did not convert records their disposition and leaves the conversion stamp alone. "It did not sell" must not be the write that makes a call converted.
A wrong key404, not 403. A 403 would confirm the call id is real, which turns the endpoint into an oracle for anyone with a list of guesses.
The URL and the key are given to the buyer with the call — {{postback.url}} and {{postback.key}} in Macros. See Ping and post for the protocol.

Adjustments

An adjustment corrects a figure. Use it when a buyer short-pays and you agree, when a publisher is owed a make-good, or when a rate was wrong on the day. It changes the record and nothing else.

Set a figure, then let it go again
# Correct one side and leave the other alone
curl -X POST "https://api.buy3.io/api/v1/calls/0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11/adjust" \
  -H "Authorization: Bearer $BUY3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "revenueCents": 3500, "reason": "Acme short-paid — agreed make-good" }'

# And later, hand that side back to the frozen terms
curl -X POST "https://api.buy3.io/api/v1/calls/0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11/adjust" \
  -H "Authorization: Bearer $BUY3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "revenueCents": null, "reason": "Correction withdrawn — buyer paid in full" }'
200 OK (the history entry)
{
  "adjustment": {
    "at": "2026-09-21T10:04:55.310Z",
    "by": { "kind": "api_key", "id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2190", "name": "API key b3_live_Zk3q" },
    "reason": "Acme short-paid — agreed make-good",
    "before": { "revenueCents": 4200, "payoutCents": 2750 },
    "after": { "revenueCents": 3500, "payoutCents": 2750 }
  }
}
You sendIt means
revenueCents: 3500Set that side to 35.00 and keep it there.
revenueCents: nullClear the correction. That side goes back to whatever the frozen terms compute — the buyer's price if the call converted, nothing if it did not.
Nothing for that sideLeave it alone, correction and all.
In the console these are the three buttons on each side of the Adjust revenue or payout dialog: Leave alone, Set to and Clear correction.
  • reason is required, between 3 and 500 characters. An adjustment with no reason is a figure nobody can defend three months later.
  • Send revenueCents, payoutCents or both. Sending neither, and clearing neither, is refused.
  • A call may be adjusted 50 times. Each is appended to the call's history with who made it, when, why, and the figures before and after — visible under Adjustments in the call drawer.
  • A figure above $1,000,000 on one call is refused. That is a typo, not a correction.

A correction sticks

  • The engine only ever converts forwards. Left to itself it moves a call from not-converted to converted and never back. Only an explicit converted: false takes it back, and because these are books that is a correction rather than a clawback.
  • null and "not sent" are deliberately different. Not sent leaves the kept figure alone, which is what every caller that is not correcting it wants; null removes it.
  • A cleared side is recorded as cleared in the history, so it reads "cleared" rather than showing a figure nobody typed.
  • The current corrections on a call are corrections on the call detail, and the console shows a note on each side of the adjust dialog when one is standing.

What moves with a correction

  • Your reports change. A report covering that date is computed from the calls as they stand now, so it follows the correction. Export what you invoice from — see Exports.
  • Cap counters move by the difference, and only when the call settled today in UTC. A counter's window has rolled by tomorrow, and subtracting yesterday's money from today's count would be a second error rather than a fix.
  • call.converted fires when the call newly converts, so a conversion pixel reports it. It does not fire again for a call that was already converted, and un-converting fires nothing at all.
  • The waterfall gains a settled step recording the recompute, with the previous figures beside the new ones.

Tags after the fact

You can add tags to a finished call — a disposition from your CRM, a quality score, the agent who took it. They are available to filters, reports and exports straight away.

Add two tags and remove one
curl -X POST "https://api.buy3.io/api/v1/calls/0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11/tags" \
  -H "Authorization: Bearer $BUY3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": {
      "crm_disposition": "policy_written",
      "crm_owner": "dallas-3",
      "qa_flagged": null
    }
  }'
200 OK
{
  "id": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11",
  "tags": {
    "gclid": "Cj0KCQjw",
    "utm_source": "google",
    "crm_disposition": "policy_written",
    "crm_owner": "dallas-3"
  },
  "tagSources": {
    "gclid": "dni",
    "utm_source": "dni",
    "crm_disposition": "api",
    "crm_owner": "api"
  }
}
  • A tag set this way is stamped api when a key sets it and user when a person does — the console shows By hand. It can never pass for something the publisher sent or the router observed, which is what makes tag provenance worth anything.
  • Tags are merged, not replaced. A key with a null value removes that tag; keys you do not mention are untouched.
  • 40 keys per request, 80 on a call in total. Keys are up to 64 letters, digits, ., - and _; values are up to 512 characters and must be a string, a number or a boolean.
  • Everything is stored as a string, so a filter comparing "35" with 35 is a bug that cannot happen.
  • Tagging works on a call at any time, including one still in progress — it changes no money and needs no settled books.

Worked example: a disputed call, settled

BlueSky Legal says call CA-482137 was a wrong number and should not have been billed. You agree. The call converted on duration for the frozen price, and your publisher has already been credited a revenue share of it.

  1. Open the call and read the Money section. Booked revenue, booked payout, and the frozen terms beside them — a call that paid nothing on a priced target is explained by those two being different.
  2. Listen to the recording if there is one. A wrong number is audible in ten seconds, and agreeing without listening is how a buyer learns to dispute everything.
  3. Mark this call not converted, with the note "Buyer disputed — wrong number, agreed to reverse". Revenue goes to zero.
  4. Decide the publisher's side separately. If their lane earns on their own buffer, they may be owed regardless of what the buyer pays — that is your deal with them, not the buyer's. If you have agreed a make-good, Adjust the payout with the reason.
  5. Check the day's report. If the call settled today, the buyer's and the target's counters have already moved by the difference; if it settled last week, they have not, and that is deliberate.
  6. Tell the publisher, and use a tag — crm_disposition: wrong_number — so the same question next month is a filter rather than a conversation.

Everything you did is in the activity log with your name on it, the values before and after, and the reasons you typed. That is what makes the next dispute short.

Next steps