Skip to main content

Integration

Designing reliable payment callbacks

Callbacks arrive late, twice or out of order. A practical guide to building an endpoint that stays correct anyway: authenticity checks, fast acknowledgement, idempotency and reconciliation.

· 7 min read

Why callbacks exist

A card payment does not always finish while the customer is watching. Authentication can take time, an issuer can respond slowly, and the customer can close the tab before being redirected back. The payment still reaches a final state. A callback, sometimes called a webhook, is how the payment platform tells your server what that state is.

In Rainbow Pay's API, you supply a callback URL when you create a payment. When the payment status changes, for example to pending, approved or declined, the platform sends a POST request to that URL. If your server does not answer with HTTP 200, the callback is retried.

Retries are what make callbacks reliable. They are also what make naive callback handlers unreliable. A handler that assumes each message arrives exactly once, in order, and only from the platform will eventually do the wrong thing. The rest of this article describes how to avoid that.

Treat every callback as untrusted input

Your callback URL is reachable from the internet. Anyone who learns it can send a request that looks like a payment approval. Before acting on a callback, confirm that it is genuine.

  • Verify authenticity using the mechanism described in the API documentation, and reject any request that fails the check. Compare signatures with a constant-time comparison.
  • Confirm the payment belongs to you. Look up the payment token or order reference in your own records. Ignore callbacks for payments you did not create.
  • Check the amount and currency against the order before fulfilling it.
  • Use HTTPS for the callback URL, and keep the endpoint free of any logic that exposes internal data in error messages.
Never fulfil an order on the strength of the customer's return to your success URL alone. That URL shows where the browser went, not what the payment did.

Acknowledge fast, process separately

The platform is waiting for your response. If your handler sends emails, updates stock, calls other services and only then answers, a slow dependency can push the response past the sender's timeout. The callback will then be retried, even though your work may have succeeded.

A more robust pattern is to split the handler in two. The receiving endpoint verifies the request, records it durably, for example in a database table or a queue, and returns HTTP 200. A separate worker then processes stored callbacks. If processing fails, the worker retries on its own schedule, and the platform does not need to resend anything.

Return a non-200 response only when you could not record the callback, for instance because your database is unavailable. That is the case in which you want the platform to try again.

Make processing idempotent

Because callbacks are retried, your handler will sometimes receive the same notification more than once. Processing must be idempotent: handling a message twice must have the same effect as handling it once.

  • Key your processing on the payment token and the status reported, and record which combinations you have already handled.
  • Wrap the status change and its side effects, such as marking an order paid, in a single database transaction, or use a unique constraint so a second attempt cannot create a second effect.
  • Make side effects that leave your system, such as a shipping request or a customer email, depend on a state change in your own records, not on the arrival of a message.

The same principle applies in the other direction. When your server creates a payment or a refund and a network error hides the response, retrying blindly can create a duplicate. Check the state of the operation through the API before sending it again.

Handle messages that arrive out of order

Network delays and retries mean that a pending notification can arrive after an approved one. If your handler simply writes whatever status it receives, an order can move backwards from paid to pending.

Model payment status as a state machine with allowed transitions. Final states, such as approved or declined, should not be overwritten by an earlier, non-final state. When a callback reports a transition your model does not allow, do not apply it; log it and, if it matters, check the current status through the API.

Refunds and disputes add further transitions after a payment is approved. Plan for them in the same model rather than bolting them on later.

Reconcile against the API

Callbacks tell you that something changed. The API tells you what the current state is. Use both.

  1. Before a high-value or irreversible action, such as releasing goods, retrieve the payment by its token and confirm the status.
  2. Run a scheduled job that looks for payments left pending in your records for longer than you expect, and queries their status. This catches callbacks your endpoint missed during an outage.
  3. Reconcile your records with settlement reports on a regular cycle, so finance and engineering are working from the same numbers.

Reconciliation turns callbacks from a single point of failure into one of two independent signals. If either is lost, the other catches it.

Test the failure cases

Use the sandbox to exercise the paths that rarely occur in normal traffic: a callback that arrives twice, a handler that times out, a declined payment followed by a new attempt, and a callback for an unknown payment. Details of the callback format are in the API documentation (opens in a new tab), and the callback integration guide summarises the steps.

Ready to discuss your payment setup?

Tell us how your business accepts payments today and what you need from your next payment integration.

Cookie preferences

Choose which optional cookies we may use. Strictly necessary cookies are always active because the website cannot work without them.

  • Strictly necessary

    Security, load balancing, form protection and remembering your cookie choice.

    Always active

Read the Cookie Policy