# ZevPay Checkout Documentation

> Developer docs for ZevPay Checkout - payment gateway for African (initially Nigerian) merchants. Accept bank transfer, ZevPay ID, virtual accounts, and cards via inline SDK, standard hosted checkout, or billable invoices. One API key, signed webhooks (HMAC-SHA256), T+1 settlement.

## How to use these docs as an AI agent

Every documentation page is available in three forms:

1. **HTML** at its public URL - e.g. `https://docs.zevpaycheckout.com/api/initialize-session`
2. **Raw markdown** at the same path with a `.md` suffix - e.g. `https://docs.zevpaycheckout.com/api/initialize-session.md`. Fetch this URL to get the unrendered source.
3. **Full corpus** - `/llms-full.txt` concatenates every page in this site into a single markdown file. Useful when you want the whole context in one request.

Every HTML page also includes:
- A `<link rel="alternate" type="text/markdown" href="...md">` tag pointing at the sidecar
- JSON-LD (`TechArticle`) describing the page
- A toolbar with "Copy markdown", "Open in ChatGPT", and "Open in Claude" buttons for human users who want to escalate the page into an AI assistant

## Getting started

- Introduction: https://docs.zevpaycheckout.com/guide/introduction
- Quick Start: https://docs.zevpaycheckout.com/guide/quick-start
- Use Cases (recipes mapping business needs to the right integration; combines invoice + standard for off-platform remainders; safe live integration since no sandbox yet): https://docs.zevpaycheckout.com/guide/use-cases
- Authentication: https://docs.zevpaycheckout.com/guide/authentication
- Test Mode: https://docs.zevpaycheckout.com/guide/test-mode
- Rate Limiting: https://docs.zevpaycheckout.com/guide/rate-limiting

## Checkout options

- Overview: https://docs.zevpaycheckout.com/checkout/overview
- Inline Checkout: https://docs.zevpaycheckout.com/checkout/inline
- Standard Checkout: https://docs.zevpaycheckout.com/checkout/standard
- Invoice Checkout: https://docs.zevpaycheckout.com/checkout/invoice
- Instalment invoices (one invoice, many payments; each payment is its own transaction that settles separately on the normal T+1 cycle): https://docs.zevpaycheckout.com/checkout/invoice-instalments
- Invoice terms are editable AFTER sending, including on part-paid invoices: `due_date` (an overdue invoice returns to sent/partial once the new date is in the future), `subsequent_payment_rule` (tighten to 'full_balance' or relax to 'flexible', effective on the customer's next payment), `allow_partial_payments`, `due_date_action`, `note`, `payment_methods`, `return_url`. NOT editable after sending: line_items, tax, customer fields, invoice_number — cancel and re-issue for those. `minimum_initial_payment` cannot change once the first payment has landed: https://docs.zevpaycheckout.com/api/invoice#update-invoice
- Invoice payment terms, set at creation or PATCHed later: `allow_partial_payments` turns instalments on; `minimum_initial_payment` (kobo) is the deposit floor for the FIRST payment only; `subsequent_payment_rule` is 'flexible' (any amount afterwards) or 'full_balance' (the rest in one payment); `due_date_action` is 'keep_collecting' (default, goes overdue and still accepts payment), 'pause' or 'cancel'. Overpayment is always refused: the ceiling is the outstanding balance. The customer picks the amount on the invoice page and the account is locked to it: https://docs.zevpaycheckout.com/checkout/invoice-instalments#choosing-your-terms
- Invoice webhook events: invoice.created, invoice.sent, invoice.payment_received (fires on EVERY instalment — fulfil on is_fully_paid, not on the event arriving), invoice.paid, invoice.overdue, invoice.paused, invoice.resumed, invoice.reminder_sent, invoice.cancelled: https://docs.zevpaycheckout.com/webhooks/events
- Invoice collection control: POST /v1/checkout/invoice/{public_id}/pause (with optional `reason` shown to the customer) and /resume stop and restart collection WITHOUT closing the invoice, keeping status, history and the customer's view of what they already paid; /cancel closes it for good but past payments stay visible to the customer: https://docs.zevpaycheckout.com/checkout/invoice-instalments#stopping-collection
- Invoice reminders: POST /v1/checkout/invoice/{public_id}/remind re-sends the invoice email, chasing the OUTSTANDING balance rather than the original total on a part-paid invoice; rate limited to one per hour per invoice: https://docs.zevpaycheckout.com/checkout/invoice-instalments#chasing-payment
- Recovering from a missed webhook, ALL payment types (the canonical guide): webhooks are an optimisation, a fetch is the source of truth. Sessions → GET /v1/checkout/session/{id}/verify (or POST .../force-verify to ask the bank directly, rate limited, use per-customer not in bulk); invoices → GET /v1/checkout/invoice/{public_id}; static accounts → GET /v1/checkout/static-account/{id}/payments; virtual accounts → GET /v1/checkout/virtual-account/{public_id}; PayIDs → GET /v1/checkout/static-payid/{id}. Match on reference, NEVER on amount (two customers can pay the same figure). Make the handler idempotent on reference BEFORE adding a reconciliation pass, or one missed payment becomes two recorded ones. Do not poll aggressively: https://docs.zevpaycheckout.com/guide/webhook-recovery
- Invoice reconciliation and missed-webhook recovery: GET /v1/checkout/invoice/{public_id} is authoritative and self-sufficient — it returns `status`, `total`, `amount_paid`, `amount_outstanding` (served, not left as client-side arithmetic), `collecting`, the instalment terms, and every payment with its `payment_id` plus the settled `transaction` (reference, fees, net_amount, settlement_state). Match on `transaction.reference` or `payment_id`, NEVER on amount: a flexible instalment invoice can legitimately take the same amount twice. Treat the fetch as the source of truth and webhooks as an optimisation; a daily pass over open invoices closes any gap: https://docs.zevpaycheckout.com/api/invoice#recovering-from-a-missed-webhook
- Invoice reconciliation: GET /v1/checkout/invoice/{public_id}/payments lists every instalment with its own reference, fee, net and settlement_state; GET .../payments/{payment_id} verifies one. Fulfil on `is_fully_paid`, never on invoice.payment_received arriving, because it fires on every instalment: https://docs.zevpaycheckout.com/checkout/invoice-instalments#reconcile
- Invoice activity log: GET /v1/checkout/invoice/{public_id}/events is an append-only record of what was done to an invoice, with `actor` = api | dashboard | system | customer, which answers whether an integration or a person caused a change: https://docs.zevpaycheckout.com/checkout/invoice-instalments#the-activity-log
- Invoices only record payments made through ZevPay (no offline/external payment recording, no negative line items): https://docs.zevpaycheckout.com/checkout/invoice#payments-made-outside-zevpay
- Collecting an invoice balance settled outside ZevPay (cancel invoice + standard checkout session, same secret key): https://docs.zevpaycheckout.com/checkout/invoice#collecting-a-balance-settled-outside-zevpay

## Payment methods

- Overview: https://docs.zevpaycheckout.com/payment-methods/overview
- Bank Transfer: https://docs.zevpaycheckout.com/payment-methods/bank-transfer
- ZevPay ID: https://docs.zevpaycheckout.com/payment-methods/zevpay-id
- Crypto (stablecoin payments; assets are always identified as ASSET:NETWORK pairs — today USDT:TRON and USDC:BASE — same asset on another network is a different pair, reconcile on both; settle-in-crypto payouts carry a flat per-payout withdrawal fee in the asset (admin-set per asset, shown on the key's crypto settings; payout breakdown = collected/fee/net; payouts smaller than the fee are held to accumulate); per-asset minimums; settle in Naira or same asset to a verified wallet; gradual rollout): https://docs.zevpaycheckout.com/payment-methods/crypto
- Choosing payment methods per checkout (payment_methods incl. "crypto", crypto_assets narrowing e.g. "USDT:TRON"; same on initialize, inline SDK and invoices; crypto auto-added when eligible ONLY when payment_methods is omitted — an explicit list is honoured exactly, so a list without "crypto"/crypto_assets excludes it; platform-disabled methods removed unless allow-listed): https://docs.zevpaycheckout.com/payment-methods/overview#how-availability-is-decided
- Card: https://docs.zevpaycheckout.com/payment-methods/card

## Core concepts

- Checkout Sessions: https://docs.zevpaycheckout.com/guide/checkout-sessions
- Payment Lifecycle: https://docs.zevpaycheckout.com/guide/payment-lifecycle
- Refunds (merchant REQUESTS from the dashboard transaction page; only while the payment is still pending settlement — never after it is batched/paid out; the request immediately holds it out of settlement, ZevPay returns the money, then charge.refunded fires and the transaction shows refunded; a declined request returns the payment to the next settlement; supported on bank_transfer, card, palmpay and static_account — ZevPay ID and crypto go through support; static accounts also have an API): https://docs.zevpaycheckout.com/guide/payment-lifecycle#refunds
- Virtual PayID: https://docs.zevpaycheckout.com/guide/virtual-payid
- Virtual Accounts: https://docs.zevpaycheckout.com/guide/virtual-accounts
- Static NGN Accounts (permanent per-customer NUBANs; NOT a checkout payment method and not in payment_methods — server-side API + dashboard only; enabled for selected merchants on request via business support, else 403 STATIC_ACCOUNT_NOT_ENABLED; creation needs customer_name + identity_type bvn|nin|rc + identity_number, name is verified against the record (IDENTITY_MISMATCH on failure); every transfer books instantly and fires charge.success with channel "static_account" + a static_account object; settles T+1 to the owning API key's settlement destination; pause/resume/delete): https://docs.zevpaycheckout.com/guide/static-accounts
- Transfers: https://docs.zevpaycheckout.com/guide/transfers

## API reference

- Overview: https://docs.zevpaycheckout.com/api/overview
- Initialize Session: https://docs.zevpaycheckout.com/api/initialize-session
- Select Payment Method (bank_transfer returns `bank_code` plus a `banks` array of every bank the session can be paid into, the active one flagged `current`; render it as a change-bank control, never hardcode the list): https://docs.zevpaycheckout.com/api/select-payment-method
- Selecting a different payment method retires the previous one (the old account number or hosted order stops accepting payment), so never keep showing it after a switch: https://docs.zevpaycheckout.com/api/select-payment-method#behavior
- Switch Bank (POST /v1/checkout/session/:sessionId/switch-bank with a `provider` from `banks`; same session, amount and reference, new account number; the previous account is retired first so a session can never be paid twice, so replace the number on screen immediately; errors BANK_UNAVAILABLE 400, BANK_SWITCH_FAILED 503; hosted checkout, inline and invoice pages all expose it to customers already): https://docs.zevpaycheckout.com/api/select-payment-method#switch-bank
- Verify Session: https://docs.zevpaycheckout.com/api/verify-session
- Get Session: https://docs.zevpaycheckout.com/api/get-session
- Static PayID: https://docs.zevpaycheckout.com/api/static-payid
- Dynamic PayID: https://docs.zevpaycheckout.com/api/dynamic-payid
- Virtual Account (the minting bank is NOT fixed and rotates across partner banks — always show the per-account `bank_name` (+ `bank_code` for a programmatic picker) from the response; `provider` is an internal key, deprecated, never display it; same rule for the `bank_name` returned by select-payment-method on bank_transfer): https://docs.zevpaycheckout.com/api/virtual-account
- Static NGN Account (GET /v1/checkout/static-account/status; POST /v1/checkout/static-account; GET list; GET /{id}; GET /{id}/payments; POST /{id}/disable | /{id}/enable; DELETE /{id}; refunds: POST /{id}/payments/{payment_id}/refund only while settlement pending (payments list has refundable flag), GET /refunds, GET /refunds/{id}, statuses requested→processing→completed|failed, rejected = settles normally; secret key only; reference is REQUIRED and idempotent (one account per customer reference); 429 STATIC_ACCOUNT_RATE_LIMITED on abuse; GET /status never 403s; 403 STATIC_ACCOUNT_SUSPENDED when access is suspended (dashboard view-only); payments carry settlement {status, expected_payout_at, settled_at, payout_id} + refundable): https://docs.zevpaycheckout.com/api/static-account
- Invoice: https://docs.zevpaycheckout.com/api/invoice
- Transfer: https://docs.zevpaycheckout.com/api/transfer
- Wallet: https://docs.zevpaycheckout.com/api/wallet
- Errors: https://docs.zevpaycheckout.com/api/errors

## SDKs

Client-side:
- Overview: https://docs.zevpaycheckout.com/sdks/overview
- React: https://docs.zevpaycheckout.com/sdks/react
- React Native: https://docs.zevpaycheckout.com/sdks/react-native
- Flutter: https://docs.zevpaycheckout.com/sdks/flutter

Server-side:
- Node.js: https://docs.zevpaycheckout.com/sdks/nodejs
- PHP: https://docs.zevpaycheckout.com/sdks/php
- Python: https://docs.zevpaycheckout.com/sdks/python
- Laravel: https://docs.zevpaycheckout.com/sdks/laravel

Plugins + tools:
- WooCommerce: https://docs.zevpaycheckout.com/sdks/woocommerce
- CLI: https://docs.zevpaycheckout.com/sdks/cli

## Webhooks

- Overview: https://docs.zevpaycheckout.com/webhooks/overview
- Verifying Signatures: https://docs.zevpaycheckout.com/webhooks/signatures
- Events Reference: https://docs.zevpaycheckout.com/webhooks/events
- charge.refunded (any refunded checkout payment; channel = bank_transfer|card|palmpay|static_account, static_account object only on that rail; same reference as charge.success; refunded_amount kobo; refund {id, reason, requested_at, completed_at}): https://docs.zevpaycheckout.com/webhooks/events#charge-refunded
- charge.success for Static NGN Accounts (channel "static_account", static_account {id, account_number, account_name, bank_name, reference, customer_name}, net_amount, payer, narration, session_id; merchant_reference = the account's reference; dedupe on reference): https://docs.zevpaycheckout.com/webhooks/events#static-ngn-account

## Optional

- Marketing site: https://zevpaycheckout.com
- Merchant dashboard: https://dashboard.zevpaycheckout.com (SSO)
- Concatenated docs (single markdown file): https://docs.zevpaycheckout.com/llms-full.txt
- Pay with PalmPay (method id "palmpay", NGN; customer approves on PalmPay's page — hosted checkout redirects out and back, inline/invoice open a new tab and confirm in place; charge.success channel "palmpay"; standard T+1 settlement; rolling out gradually): https://docs.zevpaycheckout.com/payment-methods/palmpay
