Skip to main content
The notable changes to Ringup’s API, tools, and docs. The request and response shapes are versioned in the base path (/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 inside transfer_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 as cs_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 now pending | 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 marked failed.
  • processor_unavailable (424). A server error, a timeout, or a lost connection. The outcome is unknown: the payment may have succeeded. The charge stays pending while we ask the processor what really happened, and the result reaches you on the webhook.
Retrying is safe and needs nothing from you. The idempotency key is derived from the Checkout Session and the card, so repeating the same 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

  • charge now returns 402 on a decline, in the same error envelope as every other failure, carrying decline_code, advice_code, processor_code, and the sentence your agent can speak. It previously returned 200 with a failure block, which meant an ordinary if (!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_code is Ringup’s own vocabulary, identical across processors. processor_code carries 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: identify at the greeting, then charge a recognized caller’s saved card, or send_link a first-time caller. Nothing transfers, and no card data touches your agent.
  • charge is 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 200 with status: failed at the time; see the later entry that made them a 402.)
  • 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

  • identify is 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_checkout returns payment_required as required, optional, or none. 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.