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
- Your server creates a session with your secret key.
- You redirect the customer to the returned
checkout_url. - The customer selects a method and pays on our hosted page.
- The customer is redirected to your
callback_urlwith the status. - 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.
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 verificationimport 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"]$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.
{
"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.
window.location.href = data.checkout_url;Or link to it directly:
<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-abc123Your 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=....
| Parameter | Values | Description |
|---|---|---|
status | success | Payment completed. |
status | cancelled | Customer cancelled or closed the page. |
status | expired | Session expired before payment. |
reference | string | Our transaction reference. Present on success. |
merchant_reference | string | Your 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.
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
| 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 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_assets | string[] | No | Narrow the crypto assets offered, for example ["USDT:TRON"]. |
line_items | array | No | Optional 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, sameamount, 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, differentamount: 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
| 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 |