Skip to main content

Payment flow

A ZenPays payment has three participants: your server, your customer, and us. You create an intent, your customer completes it, and we confirm the outcome and settle the funds.

Four swimlanes — your server, your customer, ZenPays and the payment provider. Your server creates a payment intent and redirects the customer to the hosted checkout, where they pick a method and confirm. ZenPays routes the payment to a provider, the provider calls back with the outcome, funds are credited net of fees, and your server receives a payment.succeeded webhook.
How a payment moves from intent to settled funds

The steps

  1. You create a payment intent. POST /api/v1/payment-intents with an amount and currency returns an intent in requires_payment_method and a hosted checkout URL. The intent expires after 20 minutes.

  2. Your customer completes it on the hosted checkout. They choose a payment method from the options available for their currency and country.

  3. We route it. When the checkout page loads payment options, we select a provider for that corridor. Which provider, and how it is chosen, is our concern — you never need to name one.

  4. The customer confirms. The intent moves to processing under a lock, so a double submit cannot create two payments.

  5. The provider calls back. We record the outcome on the transaction.

  6. Funds are credited. Your wallet is credited net of fees before the intent is marked succeeded — so a succeeded intent always means the money reached your balance.

  7. You receive a webhook. payment.succeeded (or payment.failed) is delivered to your endpoint. See Webhooks.

Status transitions

An intent starts at requires_payment_method, moves to processing on confirmation, and ends at succeeded.

There is no failed intent status

A payment that fails returns the intent to requires_payment_method so the customer can try a different method. The intent only leaves that state for succeeded, or for canceled when it expires or is cancelled outright.

Judge a payment by its transaction status, or by the webhook you receive — not by waiting for an intent status that never arrives.

The transaction underneath the intent carries the detail: initiatedprocessingsuccess or failed, plus states for the cases in between such as waiting_bank and waiting_customer.

What to build against

Treat the webhook as the source of truth, not the redirect. A customer can close the tab before being returned to your site, and the payment still succeeds — the webhook is what tells you.

Poll GET /api/v1/payment-intents/{id} only as a fallback if you have not received a webhook.