Skip to content

Standard Checkout

Create a checkout session on your server, redirect the customer to our hosted page, and handle the result when they return. This keeps your secret key on the server and lets us host the payment page.

If you want the checkout to appear as a modal on your own page, use Inline Checkout.

How it works

  1. Your server creates a session with your secret key.
  2. You redirect the customer to the returned checkout_url.
  3. The customer selects a method and pays on our hosted page.
  4. The customer is redirected to your callback_url with the status.
  5. Your server verifies the payment before fulfilling the order.

Step 1: Create a session

Call the initialize endpoint from your server with your secret key.

js
const response = await fetch('https://api.zevpaycheckout.com/v1/checkout/session/initialize', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'sk_test_your_secret_key',
  },
  body: JSON.stringify({
    amount: 500000, // ₦5,000 in kobo
    currency: 'NGN',
    email: 'customer@example.com',
    customer_name: 'John Doe', // optional
    callback_url: 'https://yoursite.com/payment/callback',
    reference: 'order_12345', // optional
    metadata: { orderId: '12345' }, // optional
  }),
});

const { data } = await response.json();
// data.checkout_url  ->  https://secure.zevpaycheckout.com/{sessionId}
// data.session_id    ->  save this for verification
python
import requests

response = requests.post(
    "https://api.zevpaycheckout.com/v1/checkout/session/initialize",
    headers={
        "Content-Type": "application/json",
        "x-api-key": "sk_test_your_secret_key",
    },
    json={
        "amount": 500000,
        "currency": "NGN",
        "email": "customer@example.com",
        "customer_name": "John Doe",
        "callback_url": "https://yoursite.com/payment/callback",
        "reference": "order_12345",
    },
)

data = response.json()["data"]
checkout_url = data["checkout_url"]
session_id = data["session_id"]
php
$ch = curl_init('https://api.zevpaycheckout.com/v1/checkout/session/initialize');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'Content-Type: application/json',
    'x-api-key: sk_test_your_secret_key',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'amount' => 500000,
    'currency' => 'NGN',
    'email' => 'customer@example.com',
    'customer_name' => 'John Doe',
    'callback_url' => 'https://yoursite.com/payment/callback',
    'reference' => 'order_12345',
  ]),
  CURLOPT_RETURNTRANSFER => true,
]);

$response = json_decode(curl_exec($ch), true);
$checkoutUrl = $response['data']['checkout_url'];
$sessionId = $response['data']['session_id'];

The response returns the hosted page URL and the identifiers you need later.

Every response from our API is wrapped in a { "success": true, "data": {...} } envelope. Read the fields off data, as the code samples above do.

json
{
  "success": true,
  "data": {
    "session_id": "ecc48011-e36e-4741-9b99-657f4a1ee86e",
    "reference": "ZVP-CKO-S-abc123",
    "merchant_reference": "order_12345",
    "checkout_url": "https://secure.zevpaycheckout.com/ecc48011-e36e-4741-9b99-657f4a1ee86e",
    "amount": 500000,
    "currency": "NGN",
    "expires_at": "2026-01-01T12:30:00.000Z",
    "merchant_name": "Your Business Name",
    "enabled_payment_methods": ["bank_transfer", "payid"]
  }
}

Reusing a still-active session with the same reference and amount returns this exact same shape (including checkout_url), so you never have to special-case it. See Idempotency.

Step 2: Redirect the customer

Send the customer to checkout_url.

js
window.location.href = data.checkout_url;

Or link to it directly:

html
<a href="https://secure.zevpaycheckout.com/ecc48011-e36e-4741-9b99-657f4a1ee86e">
  Complete payment
</a>

Step 3: Handle the callback

After the customer finishes or leaves, we redirect them to your callback_url with query parameters.

https://yoursite.com/payment/callback?status=success&reference=ZVP-CKO-S-abc123

Your callback_url can carry its own query parameters. We append ours properly, so https://yoursite.com/cb?order=123 comes back as https://yoursite.com/cb?order=123&status=success&reference=....

ParameterValuesDescription
statussuccessPayment completed.
statuscancelledCustomer cancelled or closed the page.
statusexpiredSession expired before payment.
referencestringOur transaction reference. Present on success.
merchant_referencestringYour own reference from initialize, echoed back on every outcome (success, cancelled, expired) when you set one. Lets you map the return to your order without having stored our session reference.

Step 4: Verify the payment

Always verify

The callback query parameters are not proof of payment. Confirm every payment from your server before you fulfil an order.

js
app.get('/payment/callback', async (req, res) => {
  const { status } = req.query;

  if (status === 'success') {
    const verification = await fetch(
      `https://api.zevpaycheckout.com/v1/checkout/session/${sessionId}/verify`,
      { headers: { 'x-api-key': 'sk_test_your_secret_key' } },
    );

    const { data } = await verification.json();

    if (data.status === 'completed') {
      await fulfillOrder(data.reference, data.amount);
      return res.redirect('/order/success');
    }
    return res.redirect('/order/pending');
  }

  if (status === 'cancelled') return res.redirect('/checkout');
  if (status === 'expired') return res.redirect('/checkout?error=expired');
});

Webhooks and terminal states

A successful payment fires a charge.success webhook, which is the signal you should fulfil on. It is the only webhook a standard checkout session emits today.

There is no session.expired or charge.failed webhook. For non-success outcomes, either act on the callback status (cancelled / expired) or poll verify for the session's terminal state. A session that is never paid expires after 30 minutes; a subsequent verify returns status: "expired".

Initialize parameters

ParameterTypeRequiredDescription
amountintegerYesAmount in kobo.
emailstringYesCustomer's email address.
currencystringNo"NGN" (default) or "USD".
callback_urlstringNoWhere to redirect the customer after checkout.
referencestringNoYour unique reference. Used for idempotency, see below.
customer_namestringNoShown in the dashboard instead of "Anonymous".
metadataobjectNoCustom data attached to the session.
payment_methodsstring[]NoMethods to offer, for example ["bank_transfer", "payid"]. Defaults to the methods on your key. An explicit list is honoured exactly (crypto joins automatically only when this is omitted); include "crypto" to offer it, or ["crypto"] alone for a crypto-only page. See Payment Methods.
crypto_assetsstring[]NoNarrow the crypto assets offered, for example ["USDT:TRON"].
line_itemsarrayNoOptional line items, each { name, description?, quantity, amount } with amount in kobo. Renders an itemised summary on the page.

Authentication

Both key types can call initialize. Use your secret key from a server, which is the pattern shown here. A public key also works, in which case its allowed domains are enforced against the request origin. Never put a secret key in client-side code.

Idempotency

The reference you pass makes initialize safe to retry, and it is amount-aware:

  • Same reference, same amount, while a session is still active: we return the existing session rather than creating a second one. A network retry can never produce two live payment links.
  • Same reference, different amount: the charge has changed, so we expire the old session and create a fresh one for the new amount. The old link stops working immediately, which means a customer holding it can never pay an outdated amount.

Use a stable reference per order or per balance you are collecting. If what the customer owes changes, just initialize again with the same reference and the new amount and share the fresh link.

Session lifetime

A checkout session is valid for 30 minutes. After that the hosted page shows an expired state with a link back to your site, and the callback status is expired.

Compared to Inline Checkout

AspectStandardInline
Session creationServer-side, secret keyBrowser, public key
ExperienceFull-page redirectModal on your page
Server requiredYesNo
Result deliveryCallback URLJavaScript callback
Best forServer-rendered stores, payment linksVanilla JS and single-page apps

ZevPay Checkout Developer Documentation