Developer Documentation

Getting Started with ZenithPay

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.

Quick Start

Follow these simple steps to integrate ZenithPay into your application:

1

Sign Up

Create your free ZenithPay account and get instant access to your dashboard.

2

Get API Keys

Open the Developer page in your dashboard for your merchant token.

3

Integrate

Use our API to start accepting payments in your application.

API authentication

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.

Dedicated Account Assignment

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)

Authentication Required

Replace YOUR_AUTH_TOKEN with your actual bearer token. All API requests must include proper authentication headers.

Request Parameters

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
email Required String Customer's email address (must be valid)

Response Example

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"
}

Complete Dedicated Virtual Account guidance

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.

Pay With Transfer (PWT)

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.

Retry the same payment safely

  • Choose one Idempotency-Key for one initialization and preserve the same request body when retrying it.
  • A genuinely new payment needs a new key. A changed body under the existing key may conflict.
  • If initialization is in flight or its provider result is uncertain, keep the original key and reference. Contact support for recovery; do not generate fresh keys to bypass the uncertainty.
  • Keep the returned orderNo, checkout_link and status_url. Initialization is not payment confirmation.

Existing merchant endpoints

Method and pathPurpose
POST /api/transaction/initializeCreate one payment intent
POST /api/transaction/statusQuery an owned order using order_no
POST /api/transaction/callback-diagnosticsInspect callback diagnostics for an owned order
POST /api/transaction/callback-retryExplicit 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.

One master integration package: PWT + DVA

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.

Merchant webhooks

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.

HeaderPWTDedicated Account
X-Zenithpay-TimestampUnix timestampUnix timestamp
X-Zenithpay-SignatureHMAC-SHA256 hexadecimal digestHMAC-SHA256 hexadecimal digest
X-Zenithpay-Event and X-Zenithpay-Event-IdEvent label and durable event IDNot required by the legacy DVA sender; deduplicate by payment reference
Source address metadataX-Zenithpay-Source-IP when configuredX-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.

One payment, one business credit

  1. Authenticate the callback and match its payment reference to the expected merchant order and amount.
  2. Record the event/reference deduplication decision and business credit atomically, using durable uniqueness constraints.
  3. If the same event arrives again, return the already accepted result without crediting it again. For PWT, keep both event identity and payment-reference business uniqueness in mind.
  4. Return HTTP 200 after durable acceptance. PWT accepts other 2xx responses, but the legacy Dedicated Account sender specifically expects 200.

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.

Payment confirmation and settlement

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.

Testing your integration

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.

  • Test HMAC verification and duplicate-event handling in a receiver copy with an isolated test database.
  • Submit the same synthetic event twice and confirm one durable credit entry.
  • For live acceptance, observe a genuine payment and match the sender acknowledgement to the merchant receiver and ledger. Do not replay a live financial callback as a test.