/v1): within a version, changes are additive, and a breaking change
ships under a new version. See the stability note in the API introduction.
Transfer by phone number, a compact checkout token, and self-describing session ids
Transfer to a phone number
create_checkout now returns two transfer destinations on every response where payment is
required: transfer_to (the SIP URI, unchanged as the default) and the new transfer_to_number,
a plain phone number for platforms that can only transfer calls to a number. The call arrives
without the token and the payment line resolves the session by the caller’s number. Pick one when
you set your platform up; nothing else about the integration changes. Additive: existing
integrations keep working untouched.The checkout token slimmed to a pure credential
The token insidetransfer_to now carries only the session id and an expiry (about 122
characters, down from 332), so it fits transports that cap SIP URI length. Everything the old
token carried is read from the session it names. You never parse the token, so no code changes.Session ids carry their environment
New checkout sessions mint ascs_test_… and cs_live_…, so any id you meet in a log, a
webhook, or a support thread says which environment it belongs to. Existing ids keep working.A guide for platforms without a Ringup integration
Custom (REST API): take payments from any platform that can make HTTPS calls and transfer a call. Two API calls and one transfer, end to end.Vapi setup moves to the dashboard, Clover goes live, and the dashboard gains Logs
Add Vapi from the dashboard, no code
Wiring a Vapi agent is now a dashboard flow: connect Vapi once, pick your agent, and paste the one prompt line Ringup gives you. Ringup attaches the payment tool with the right server URL and secret, so there is no tool JSON to hand-write. See Dashboard setup.Test-mode tip: duplicate your live agent in Vapi and connect the copy (for example Agent (Ringup test)), so your production agent is untouched while you test.Clover merchants can connect
Clover is now a live processor on the hosted connect page. A Clover merchant pastes their public token, private token, and Merchant ID from their Clover dashboard, and Ringup settles on their Clover account. See Clover. Square’s one-click OAuth is unchanged.Per-vendor Logs in the dashboard
The dashboard has a Logs tab: every call grouped into one row and tagged by how far the payment got (reach, line, charge), scoped to your account. It is the fastest way to see a call land as paid or spot where one failed.Docs reorganized
Integration guides are now under Voice integrations, and processor guides under Merchant integrations, each with a page per supported option.Infrastructure trouble is returned, and the charge statuses are renamed
A charge no longer says “fine” when we do not know
Breaking: the charge statuses are nowpending | succeeded | failed.created became pending. Same position in the lifecycle, better name: created described our
record, while pending describes your problem, which is that the outcome is not settled and you
should not start a second payment.completed became succeeded, and the webhook events moved with it: checkout.completed is
now checkout.succeeded. One word for one idea across the object and the webhook, and the word is
the one you already know from other payments APIs. checkout.failed and merchant.connected are
unchanged, as is the Checkout Session’s own complete status, which is a different fact: the
session finished, which is not the same as a payment succeeding.charge now returns infrastructure failures instead of absorbing them. Previously a rate limit
or a server error from the merchant’s processor came back as 200 with the charge left unsettled,
which told you the call had gone fine while nothing had. Two codes are now reachable that were
documented but never emitted:rate_limit(429). The processor refused the request before touching a card. Nothing was charged and the charge is markedfailed.processor_unavailable(424). A server error, a timeout, or a lost connection. The outcome is unknown: the payment may have succeeded. The charge stayspendingwhile we ask the processor what really happened, and the result reaches you on the webhook.
charge call cannot charge twice. There is no header to
send.processor_unavailable no longer suggests a pay link. Its agent_action is now null, and the
spoken line claims no outcome: “I’m still confirming that payment. I’ll send you a confirmation as
soon as it comes through.” A pay link would open a second payment under a different key, which is
how an unknown charge becomes a double charge.One name per concept, cards move to the session
The session owns the cards, and every name is the same everywhere
One release, five endpoints, and one rule behind all of it: one name per concept, one card shape everywhere, and every response echoes your own identifiers so reconciliation never needs a second call. Here is what to change.identify no longer returns cards. It takes { "caller_phone": "+1..." } in the body (the
field was phone, and the number never rides a header) and returns { known, consumer }, where
consumer is { id, first_name, last_name, email } or null. Whether a saved card can be charged
depends on the merchant’s processor, and identify has no merchant, so any card it returned would
be wrong for some merchant. An unknown caller is known: false at 200, the ordinary first-call
outcome. See Caller Identity.Cards now come from create_checkout, which knows the merchant. cards[] holds
{ instrument_id, brand, last_four, expiry }, newest first, filtered to what is chargeable at that
merchant, and default_card_id names the newest. Empty means text a link. Read
Checkout Session before you offer a card out loud.Renamed request fields on create_checkout: phone is now caller_phone, order_ref is
order_id, closing_message is success_message, call_id is platform_call_id, and
return_uri is gone in favour of return_to. line_items and screen are removed. order_id is
the amount authority: with it, the total is read from the merchant’s own order and amount_cents
is optional and never trusted over the order (a mismatch is 409 amount_mismatch).Renamed response fields on create_checkout: verdict is now payment_required and
transfer_uri is transfer_to. The response also carries amount_source ("order" or
"request"), expires_at, and your metadata echoed back.A merchant with no processor connected is no longer an error here. It returns 201 with
payment_required: "none". Delete any merchant_not_connected branch you wrote around
create_checkout. See Payment policy for what "none" means and why
you respond to both of its meanings the same way.New canonical read: GET /v1/checkout_sessions/{checkout_session_id}. The top-level status is
now session_status, because the charge has a status of its own; return_uri is return_to and
processor_charge_ref is processor_payment_id.send_link takes only { "checkout_session_id": "cs_..." }. Drop phone, merchant_id and
amount_cents: they all live on the session, so a link can no longer carry a total nobody derived
from an order or be texted to a number the session was not opened for. In the response, url is
now pay_url and mode is payment_required. Read sent: it is false when a repeat send
inside a short window returned the link without texting again, and an agent that ignores it tells
the caller “I have texted you” when nothing went out.charge responds with status, replay, charge_id, processor_payment_id, and your
merchant_id, order_id and platform_call_id. Statuses are created, completed, failed.
created says what we know, that a charge record exists and the processor has not confirmed;
pending would have asserted something about their state we were never told. The request is
unchanged: checkout_session_id plus an explicit instrument_id, which is still never defaulted.checkout.failed now fires on a decline, not only on an expiry. A decline previously emitted
nothing, which left the one failure you most needed to hear about silent. The payload carries
reason ("declined" or "expired") and last_payment_error, the same error object the 402
returned, so one parser handles both paths. Across every webhook payload, call_id is now
platform_call_id. See Webhooks.Declines return 402
A declined card is an error, not a result
chargenow returns402on a decline, in the same error envelope as every other failure, carryingdecline_code,advice_code,processor_code, and the sentence your agent can speak. It previously returned200with afailureblock, which meant an ordinaryif (!response.ok)check read a declined payment as a success. This is a breaking change within/v1, made deliberately and before any integration depended on it.- Reading a session back is unchanged at
200: it is a read, so it reports whatever the outcome was. decline_codeis Ringup’s own vocabulary, identical across processors.processor_codecarries the processor’s raw value for tracing.
Two integration modes
Hosted and in-agent API, on every platform
- Every integration is now documented two ways as tabs: hosted (Ringup takes the payment on a transfer or handoff, set up from the dashboard, you write no payment logic) and in your agent (you keep the caller in your own agent and add Ringup’s tools). See How it works.
- The in-agent API path is tools-first and platform-native:
identifyat the greeting, thenchargea recognized caller’s saved card, orsend_linka first-time caller. Nothing transfers, and no card data touches your agent. chargeis now the recommended path on OpenAI Realtime, whose hosted transfer depends on carrier-leg SIP REFER support.
Transfer validation and setup guides
Live-validated transfers, webhook setup, and Test it on every guide
- Call transfer is validated end to end on live calls on Vapi, Retell, and ElevenLabs; each guide’s Validation status says exactly what ran.
- The ElevenLabs guide now reflects the platform’s transfer model: a SIP-trunk number and a
standing payment address pinned in the agent’s transfer rule, with the order registered
mid-call by
create_checkout. - Webhook setup is a first-class setup step on every integration guide, dashboard and API paths alike: add your endpoint under Settings, then Webhooks, and copy the signing secret.
- Every integration guide ends its setup with a Test it section: one full sandbox payment before go-live, on shared fixtures. See Testing.
Interactive docs
Interactive API reference, errors, and language tabs
- A live playground on every tool in the API reference: call the sandbox from the page.
- A new Errors page cataloguing every code Ringup can return. (Declines
were a
200withstatus: failedat the time; see the later entry that made them a402.) curl, Node, and Python tabs on the Quickstart.- A rendered flow diagram on How it works, and a “Next steps” trail on every page.
Identity
Recognize at the start of the call
identifyis documented and wired to run at the greeting, not at the payment moment, so a returning caller is greeted by name and never re-asked for anything recognition already knows. See Caller Identity.- Caller and merchant identity are now two concepts: who is paying (Caller Identity) and which business they reached (Merchant Identity).
- The Checkout Session is the object every payment turns on:
create_checkoutreturnspayment_requiredasrequired,optional, ornone. See Checkout Session.
Integrations
Eight platforms and a keyless sandbox
- Integration guides for Vapi (native handoff and call transfer), Retell, ElevenLabs, Deepgram, LiveKit, OpenAI Realtime, Pipecat, and Twilio ConversationRelay.
- A shared test sandbox that is live for every account from the first command, with ready-made merchants and a recognized caller. No keys to mint. See Testing.
- Charges settle on each merchant’s own payment processor: Ringup never becomes the merchant of record and never holds the money.