Skip to main content
Ringup posts an event to your server when a Checkout Session finishes, paid or not. Your system learns the outcome out of band, without waiting on the call. It is the same across every platform, so you write one handler.

Register your endpoint

In the dashboard, open Webhooks and add your HTTPS endpoint. Copy the signing secret from the confirmation: it is shown once and cannot be recovered afterwards. Ringup then delivers every event below to it, signed. You can register more than one endpoint, disable or delete one at any time, and each environment (Test and Live) keeps its own list. The same page shows each endpoint’s recent deliveries, with attempt counts and the last error when one failed. Prefer to script it? The API does the same thing, with your environment’s key:
The signing secret is in the response, shown once here too.

The envelope

Every event has the same shape:

Event catalog

The catalog is deliberately small. A first-time link payment is a checkout.succeeded like any other, not a separate event.

checkout.succeeded

The one you build against. Its data carries everything you need to mark an order paid:
Match on the order_id you passed (or platform_call_id), store the charge_id, and flip your order to paid. A payment made with Apple Pay or Google Pay on the pay page arrives as this same event with wallet naming the wallet and card null: a wallet payment saves no card, so there is no brand or last four to report. Label it “Apple Pay” or “Google Pay” in your own surfaces rather than treating the missing card as a gap.

checkout.failed

It fires on a decline as well as an expiry. A caller whose card is declined and who then hangs up is the case your server most needs to hear about, so it is an event, not something you discover by polling.
reason tells the two cases apart:
  • declined. A card was charged and the issuer said no. charge_id names the attempt, and last_payment_error says why.
  • expired. The session lapsed unpaid. charge_id and last_payment_error are null: nothing was attempted, so there is no error to report.
last_payment_error is the same error object the 402 returned, so the parser you already wrote for a synchronous decline handles the asynchronous one unchanged. See Errors for every field. It omits request_id: that identifies a request, and an event is not one.

checkout.step_up_required

Ringup screens the caller’s number on every checkout. When it finds a positive risk signal, the caller’s saved card is not offered on that call: they are texted a payment link instead, which proves possession of the phone. This event tells you that happened, so the answer to “why did my caller get a link when they have a card on file” is in your logs before they ask. It fires once per session, whichever signal found the risk, and only on a real signal. A screening lookup that errors or cannot run is treated as neutral and produces no event, because a gap on our side is not a fact about your caller.
reasons carries stable codes, not prose: ported_recently when the number changed hands inside the last seven days, attestation_C or attestation_F when the carrier’s signature on the original call was poor or failed.
Nothing here is spoken to the caller, and your agent should not repeat it. The payment line already handles the moment gracefully: it offers the link warmly and never mentions checks or verification.

Charge statuses

A charge is in one of three states; the same values appear on the session and in the charge response:
  • succeeded: the charge settled. This is the only status that means money moved.
  • pending: not settled. The processor has not confirmed an outcome, and the payment may or may not have gone through, so never announce it either way.
  • failed: the charge was declined or errored; nothing was charged.

Getting it right

Three rules cover almost every webhook bug:
  1. Verify the signature. Each request carries a Ringup-Signature header, an HMAC of the raw body plus the Ringup-Timestamp. Recompute it with your endpoint’s signing secret and reject a mismatch or an old timestamp, so nobody can forge an event.
  2. Dedupe on event_id. Delivery is at-least-once: Ringup retries on any non-2xx response, so an event can arrive more than once. Treat a repeated event_id as a no-op.
  3. Acknowledge fast. Return 2xx immediately and do your work asynchronously. Ringup retries with backoff and parks a persistently failing endpoint after several attempts.
Signature scheme. Ringup-Signature is an HMAC-SHA256 over the timestamp and the raw request body, signed with your endpoint’s secret, sent as v1=<signature>. The secret is returned once, in the registration response.

Next steps

Testing

Trigger real events in test mode and watch them arrive.

API reference

Every field of every endpoint, with a live playground.