Welcome to ZenithPay's developer documentation. This guide will help you integrate our payment API into your application and connect approved payment collection services to your application.
Follow these simple steps to integrate ZenithPay into your application:
Create your free ZenithPay account and get instant access to your dashboard.
Open the Developer page in your dashboard for your merchant token.
Use our API to start accepting payments in your application.
Use the bearer token issued for your merchant account. Signed-in merchants can manage their integration through the Developer page. Keep the token on your backend.
Authorization: Bearer YOUR_MERCHANT_TOKEN
Accept: application/json
These are placeholders, not working credentials. Never place an issued token in browser code, public repositories, screenshots or shared Postman exports. Use a private environment or your server's secret storage.
API authentication and incoming webhook verification are different steps. A merchant request to ZenithPay uses the bearer header; a callback from ZenithPay uses the timestamp and raw-body HMAC signature described below.
Assign a dedicated virtual account to your customers. This allows them to make payments directly to a unique account number tied to their profile.
curl -X POST 'https://zenithpay.ng/api/dedicated_account/assign' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer YOUR_AUTH_TOKEN' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'bvn=YOUR_CUSTOMER_BVN' \
-d 'account_name=ZTS' \
-d 'first_name=John' \
-d 'last_name=Doe' \
-d 'email=customer@example.invalid'
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://zenithpay.ng/api/dedicated_account/assign',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query([
'bvn' => 'YOUR_CUSTOMER_BVN',
'account_name' => 'ZTS',
'first_name' => 'John',
'last_name' => 'Doe',
'email' => 'customer@example.invalid'
]),
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-Type: application/x-www-form-urlencoded',
'Authorization: Bearer YOUR_AUTH_TOKEN'
],
]);
$response = curl_exec($curl);
$error = curl_error($curl);
curl_close($curl);
if ($error) {
echo "Error: " . $error;
} else {
echo $response;
}
?>
const axios = require('axios');
const qs = require('querystring');
const data = qs.stringify({
'bvn': 'YOUR_CUSTOMER_BVN',
'account_name': 'ZTS',
'first_name': 'John',
'last_name': 'Doe',
'email': 'customer@example.invalid'
});
const config = {
method: 'post',
url: 'https://zenithpay.ng/api/dedicated_account/assign',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer YOUR_AUTH_TOKEN'
},
data: data
};
axios(config)
.then(response => console.log(response.data))
.catch(error => console.error(error));
import requests
url = "https://zenithpay.ng/api/dedicated_account/assign"
payload = {
'bvn': 'YOUR_CUSTOMER_BVN',
'account_name': 'ZTS',
'first_name': 'John',
'last_name': 'Doe',
'email': 'customer@example.invalid'
}
headers = {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer YOUR_AUTH_TOKEN'
}
response = requests.post(url, data=payload, headers=headers)
print(response.text)
Replace YOUR_AUTH_TOKEN with your actual bearer token. All API requests must include proper authentication headers.
| Parameter | Required | Type | Description |
|---|---|---|---|
| bvn | Required | String | Customer's Bank Verification Number (11 digits) |
| account_name | Required | String | Customer/account name; ZenithPay combines it with your business name and the provider returns the issued display name |
| first_name | Required | String | Customer's first name |
| last_name | Required | String | Customer's last name |
| Required | String | Customer's email address (must be valid) |
Success Response (200 OK)
Illustrative subset of the legacy response; use the returned values. Additional customer/identity fields may be present and must be handled privately.
{
"status": true,
"response_code": "00",
"accountReference": "EXAMPLE_ACCOUNT_REFERENCE",
"accountName": "ABBA DATABUY (ZenithPay)",
"customerEmail": "customer@example.invalid",
"bankName": "PALMPAY",
"accountNumber": "EXAMPLE_ACCOUNT_NUMBER"
}
Learn account assignment, customer mapping, returned account names, signed callback fields, transaction-reference duplicate protection, error handling and D+1 settlement in the DVA integration guide. A dedicated account can receive many payments: deduplicate each payment by its transaction reference, never by account number.
Create a payment-specific transfer account and checkout link. PWT initialization accepts amount, email and callback URL; it does not require a customer BVN field. This is separate from Dedicated Account assignment, which requires BVN and merchant approval.
Amounts in this legacy initialization request are in naira. Use the response's payable amount, fees, expiry and account details for that payment. The example below does not set your merchant's pricing.
curl -X POST 'https://zenithpay.ng/api/transaction/initialize' \
-H 'Authorization: Bearer YOUR_MERCHANT_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: YOUR_STABLE_PAYMENT_KEY' \
-d '{"amount":1000,"email":"customer@example.invalid","callback_url":"https://merchant.example.com/payment/callback","fee_bearer":"customer","meta_data":{"order_id":"YOUR_ORDER_REFERENCE"}}'
Use a public HTTPS callback endpoint on port 443 with a valid certificate. Private/reserved destinations, URL credentials and fragments are rejected; redirects are not followed. A configured callback URL is not proof the receiver accepts the callback.
Idempotency-Key for one initialization and preserve the same request body when retrying it.orderNo, checkout_link and status_url. Initialization is not payment confirmation.| Method and path | Purpose |
|---|---|
POST /api/transaction/initialize | Create one payment intent |
POST /api/transaction/status | Query an owned order using order_no |
POST /api/transaction/callback-diagnostics | Inspect callback diagnostics for an owned order |
POST /api/transaction/callback-retry | Explicit resend after receiver repair; never an automatic acceptance test |
Status, diagnostics and retry requests use JSON such as {"order_no":"YOUR_ORDER_NUMBER"} with your bearer token. Respect rate limits and keep secrets out of support screenshots. Status queries can contact the provider; they are not a database-only diagnostic.
Use the same master collection for Dedicated Virtual Account assignment and the four PWT operations. Import the collection and private environment template, then enter your existing Developer API token privately. The collection uses that token directly; website login does not issue a Developer API token.
The PWT initialization keeps the same key for the same request. Manual callback retry is disabled by default and requires deliberate opt-in for each send. Requests to the live host can create real accounts or contact receivers.
ZenithPay sends POST callbacks to your configured endpoint. Verify the signature using your existing merchant signing token (the Developer token used by the current callback sender). An outbound callback does not send that token in an Authorization header.
| Header | PWT | Dedicated Account |
|---|---|---|
X-Zenithpay-Timestamp | Unix timestamp | Unix timestamp |
X-Zenithpay-Signature | HMAC-SHA256 hexadecimal digest | HMAC-SHA256 hexadecimal digest |
X-Zenithpay-Event and X-Zenithpay-Event-Id | Event label and durable event ID | Not required by the legacy DVA sender; deduplicate by payment reference |
| Source address metadata | X-Zenithpay-Source-IP when configured | X-ZenithPay-Server-IP |
Header names are case-insensitive. Source-IP headers are metadata, not cryptographic proof. They do not replace signature verification.
HMAC-SHA256(timestamp + "." + exact_raw_request_body, merchant_signing_token)
Validate the timestamp with a bounded clock-skew window, compare the signature in constant time, and only then decode the JSON. Do not re-encode JSON before checking the HMAC. The existing callback body varies by sender path; do not assume one universal payload shape.
A timeout can happen after your receiver accepted a payment. Retries and explicit PWT resends keep the event identity, but payload snapshots can differ between legacy sender profiles. Repeated delivery or HTTP 200 log entries do not prove duplicate credit. A sender acknowledgement alone does not prove your application credited a wallet.
A successful collection is separate from settlement. Confirmed collections enter pending settlement under the existing D+1/T+1 rules. Settlement is scheduled for 06:00 Africa/Lagos on the next eligible settlement day, with weekend and configured holiday deferrals. A successful callback does not mean funds are already available for withdrawal.
The examples and downloadable collection use placeholders. A Test/Live label or an example key does not establish a provider-isolated sandbox. Ask support to confirm the environment and credentials before sending requests. Requests to the live host can create real payment accounts and callbacks.