---
title: Standard Checkout
description: Redirect customers to a hosted ZevPay checkout page. Create the session on your server with your secret key, redirect to the returned URL, then verify the result server-side.
---

# 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](/checkout/inline).

## 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.

::: code-group

```js [Node.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 [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 [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.

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

## 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
```

| Parameter | Values | Description |
|-----------|--------|-------------|
| `status` | `success` | Payment completed. |
| `status` | `cancelled` | Customer cancelled or closed the page. |
| `status` | `expired` | Session expired before payment. |
| `reference` | string | Transaction reference. Present on `success`. |

## Step 4: Verify the payment

::: warning 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');
});
```

## Initialize parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `amount` | integer | Yes | Amount in kobo. |
| `email` | string | Yes | Customer's email address. |
| `currency` | string | No | `"NGN"` (default) or `"USD"`. |
| `callback_url` | string | No | Where to redirect the customer after checkout. |
| `reference` | string | No | Your unique reference. Used for idempotency, see below. |
| `customer_name` | string | No | Shown in the dashboard instead of "Anonymous". |
| `metadata` | object | No | Custom data attached to the session. |
| `payment_methods` | string[] | No | Methods to offer, for example `["bank_transfer", "payid"]`. Defaults to `["bank_transfer", "payid"]`. |
| `line_items` | array | No | Optional line items, each `{ name, description?, quantity, amount }` with `amount` in kobo. Renders an itemised summary on the page. |

::: tip 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

If you retry initialize with the same `reference` while a session is still active, we return the existing session rather than creating a second one. Use a stable `reference` per order to make retries safe.

## 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

| Aspect | Standard | Inline |
|--------|----------|--------|
| Session creation | Server-side, secret key | Browser, public key |
| Experience | Full-page redirect | Modal on your page |
| Server required | Yes | No |
| Result delivery | Callback URL | JavaScript callback |
| Best for | Server-rendered stores, payment links | Vanilla JS and single-page apps |

## Related

- [Inline Checkout](/checkout/inline)
- [Invoice Checkout](/checkout/invoice)
- [Initialize Session](/api/initialize-session)
- [Verify Session](/api/verify-session)
- [Webhook Events](/webhooks/events)
