Payment Lifecycle
This page describes the complete flow from session creation to payment confirmation.
Flow overview
Your Server ZevPay API Customer
│ │ │
│ POST /initialize │ │
│─────────────────────────────▶│ │
│ { session_id, checkout_url }│ │
│◀─────────────────────────────│ │
│ │ │
│ Return checkout_url or │ │
│ pass public key to SDK │ │
│─────────────────────────────────────────────────────────▶│
│ │ │
│ │ POST /payment-method │
│ │◀─────────────────────────│
│ │ { account_number, ... } │
│ │─────────────────────────▶│
│ │ │
│ │ Customer pays │
│ │◀─────────────────────────│
│ │ │
│ Webhook: charge.success │ │
│◀─────────────────────────────│ │
│ │ │
│ GET /verify (optional) │ │
│─────────────────────────────▶│ │
│ { status: "completed" } │ │
│◀─────────────────────────────│ │Step-by-step
1. Initialize session
Your server calls POST /v1/checkout/session/initialize with your secret key, the amount, and customer email.
2. Customer selects payment method
The checkout UI (inline modal or standard page) presents the available payment methods. When the customer selects one, the SDK calls POST /v1/checkout/session/:id/payment-method.
For bank transfer, this returns a virtual account number. For PayID, this returns a dynamic PayID.
3. Customer completes payment
The customer transfers money (via bank or ZevPay app). We detect the transfer automatically.
4. Payment confirmed
We:
- Mark the session as
completed - Send a
charge.successwebhook to your server - The checkout UI detects the completion via polling and shows a success screen
5. You verify and fulfill
Your server verifies the payment via the webhook or the /verify endpoint, then fulfills the order.
Polling
Both the inline and standard checkout SDKs poll the /verify endpoint every 5 seconds after the customer selects a payment method. When the status changes to completed, the success screen is shown automatically.
Idempotency
- Each session can only be completed once
- Duplicate bank transfers to the same virtual account are rejected
- The
referenceparameter on initialization helps you prevent duplicate sessions
Refunds
A payment can be returned to the payer while it is still pending settlement, meaning before it is picked up by a payout batch. Request it from the transaction page in your dashboard: the Refund button is active only while the payment still qualifies.
What happens next:
- The payment is immediately held out of your next settlement, so the money stops on its way to you.
- ZevPay returns it to the payer, through the provider where that is supported and by transfer otherwise.
- You receive
charge.refundedand the transaction shows as refunded.
If the request is declined, the payment goes back into your next settlement and nothing changes for you.
Bank transfer, card, PalmPay and Static NGN Account payments can be refunded this way. ZevPay ID and crypto payments are returned by support instead. Once a payment has been paid out to you, a refund is handled with support, not from the dashboard. Static NGN Account payments can also be refunded through the API.
Error handling
| Scenario | What happens |
|---|---|
| Session expires (30 min) | Status set to expired, customer shown expiry screen |
| Transfer amount mismatch | Transfer may not be matched to the session |
| Network error during polling | SDK retries silently on next poll interval |
| Webhook delivery fails | Logged in dashboard, you should poll /verify as backup |