Outcome settlement
An outcome charge is a charge that depends on a result, such as a resolved ticket or a completed task. Settlement decides whether it stands. In payments, “settlement” usually means money reaching a bank account. Here it doesn’t: Runledger never moves money. It records the charge as pending, confirms it when the result stands, reverses it when it doesn’t, and your payment provider collects whatever stands.
The lifecycle
Every change is recorded in the charge’s status_transitions with its cause: the event that created it, a clock, a rule, a dispute or an API request.
Two clocks
Each outcome definition sets two durations, and both are stamped on every charge it creates.
confirmation_window: how long a new charge stays pending. If nothing happens, it confirms when the window closes. AI support agents typically use a few hours to a few days. Allowed range:PT0StoP30D.reversal_horizon: how long after confirmation the charge can still be reversed, for “was it really resolved?” cases such as a customer coming back a week later. When it closes, the charge locks, unless a dispute is still open. Allowed range:PT0StoP180D.
A charge confirms by default when nobody objects, the same default affiliate networks and card schemes use. Call POST /v1/outcome_charges/{id}/confirm to confirm early, for example when the end customer marks the conversation resolved.
Presets, not defaults
The clocks have no silent default. Both stay required, and a definition always stores explicit values. To save you guessing, GET /v1/outcome_definition_presets returns a recommended set for common kinds of outcome. Send preset on POST /v1/outcome_definitions and the server fills in whichever of confirmation_window, reversal_horizon and dispute_settings you leave out, stores them on the version, and echoes them back with the preset’s key. Values you send always win. Changing a preset later never changes a definition that used it. The dashboard shows the same presets as chips that fill in visible values, and an agent over MCP is told to ask you to confirm them.
The API returns each preset with a rationale, one sentence on what its values are based on, and a confidence of low, medium or high. Every preset also sets decision_window to P7D and default_decision to reverse. They are starting points: confirm them with your own customers and change what doesn’t fit. The examples on this site use support_chat.
Bounds and warnings
The hard limits above are enforced. Outside the recommended range a request still works, and the response carries warnings that say why. The dashboard and the MCP server show them too.
Validate a definition before you write it with Runledger-Dry-Run: true, or POST /v1/config/validate; see Configuration.
Outcome definitions are versioned
An outcome definition holds the event_name that reports the outcome, the criteria you agreed with your customers, the clocks, the reversal rules and the dispute settings. A plan sets its price.
Changing the criteria, clocks, rules or dispute settings creates a new version. Charges keep the terms of the version they were created under, and each charge records outcome_definition_version, so when a customer asks why they were charged, the answer is the definition they agreed to at the time. GET /v1/outcome_definitions/{outcome_definition}/versions lists every version with who made it, a summary of the change and the charge counts under it, and each version records its actor.
Reversal rules
Most reversals should happen by rule, decided in advance, so nobody argues over each charge. A rule listens for a signal event: any track() event whose name a rule names. The signal finds its charge by customer and reference, so send the same reference (a conversation or ticket ID) on the outcome and on everything that follows.
conditiontests one property of the signal event. Numbers compare numerically.applies_tolimits the rule topendingcharges,confirmedcharges, orany.actionisreverse(reverse at once) oropen_dispute(put it to you).- Rules run in order and the first match wins. Each definition takes up to 10.
A signal for a locked charge has no effect, and track() returns the warning reversal_horizon_passed. Use POST /v1/events/preview to see which rule a signal would fire before you send it.
Disputes
A dispute is the exception path, for complaints a rule didn’t settle. Open one with POST /v1/disputes when an end customer complains through your support channel, or let an open_dispute rule do it.
- A dispute on a pending charge stops it confirming.
- You are the decider. Resolve with
uphold(the charge stands; a pending charge confirms at once) orreverse. - Each dispute has a
decide_bydeadline, set by the definition’sdispute_settings.decision_window(at least 24 hours, at most 30 days). You send it with the definition, or apresetfills it in, as it does the two clocks;support_chatgives seven days.dispute.decision_duefires 24 hours before it, and a notification rule can email the people who decide. - If nobody decides in time,
default_decisionapplies. It defaults toreverse, so an ignored complaint doesn’t stand. Set it toupholdif your terms say otherwise.
A dispute that is open at the horizon
You can open a dispute until the reversal horizon passes. A dispute on a pending charge stops it confirming. A dispute on a confirmed charge holds its lock: if the horizon passes while the dispute is open, the charge stays disputed and doesn’t lock until you decide.
- The charge carries an
open_disputeblock with the dispute’sid, itsdecide_byandholds_lock: truewhen the dispute is what keeps the charge from locking. reversereverses the charge, even though the horizon has passed.upholdlocks it at once if the horizon passed while the dispute was open, withlock_reason: dispute_upheld, which the dashboard shows as “locked after dispute”. If the horizon hasn’t passed, an upheld charge goes back toconfirmedand locks when the horizon closes, withlock_reason: reversal_horizon.- If nobody decides,
default_decisionapplies atdecide_by, which can be after the horizon.
Opening a dispute on a charge that has already locked is refused.
Provider chargebacks on top-ups and invoice payments are a different thing: they happen at your payment provider and don’t change an outcome charge. See Payment providers.
What a reversal does
A reversal is its own immutable object, and GET /v1/reversals lists them. It records the reason, the source (rule, dispute or api), the actor when a person or an agent made it, and the effect on money:
Runledger reverses the charge itself and never edits a finalized invoice. A reversal applies to a whole charge; there are no partial reversals.
One charge per reference
A customer can have only one active outcome charge per outcome definition and reference. A second outcome event for the same conversation while the first charge is pending, disputed, confirmed or locked creates nothing, and track() warns duplicate_outcome_for_reference. Once a charge is reversed, a later outcome event for the same reference creates a new charge, for example when a reopened conversation is resolved again.
Evidence
Attach evidence to the outcome event: a short description, a link to the transcript, and properties such as a satisfaction score. It is stamped on the charge with the definition version and shown to whoever decides a dispute. Keep it factual. Free text is sanitized before it reaches dashboards or agent tools.
For your finance team
GET /v1/reports/revenue splits each period into recognized, pending, reversible, reversed, deferred and breakage amounts, splits recognized revenue by invoice line type (usage, outcome, overage and minimum commitment), can return the same figures by day, week or month, and each outcome definition reports its amounts and its reversal_rate over the range you ask for. GET /v1/reports/outcomes gives counts and amounts over time, grouped by status, definition, reason or source, so reversal and dispute rates don’t depend on adding up list pages. Credits and money are reported separately and never added together. These are operating figures. Confirm the accounting treatment, such as how much of the reversible amount to hold back from revenue, with your adviser.
Limits
- Reversals are whole-charge. You can’t reverse part of a charge, such as half for an escalated conversation.
- Who records disputes. You and your team record complaints with
POST /v1/disputes. Your end customers don’t open disputes themselves, and the customer view has no dispute screen. - Who decides. Disputes are decided by you and your team. A third party can’t be named as the decider.
- Where settlement runs. Settlement uses Runledger’s own plans and invoices. It doesn’t run on top of another billing system.
- Outcome charges keep the price on their outcome definition. Price rules apply to usage, not to outcome charges.
Next steps
- Checks and holds decide work before it starts, and hold what it will cost.
- Webhooks tell you when a charge confirms, reverses or locks, and when a dispute is due.
- Sandbox and test clocks lets you run the whole lifecycle in seconds.