Accept payments ↗ Get started →
Integration  /  Getting started

Build on Koodify.

Integrate once against Koodify: embed bank-transfer checkout, verify signed webhooks, split payments across zainboxes, and call stable /api/v1 routes with your merchant keys.

In a nutshell
Use pk_… in the browser for checkout. Use sk_… as X-Api-Key on your server for wallet, transactions, settlements, and billing. Prefer the /api/v1/… paths in these guides.

Quickstart

Collect money with inline checkout (pk_…), or read your zainbox from the server with a merchant secret (sk_…). Never put sk_… in client-side code.

01 / CREDENTIALS

Get your keys

After onboarding you receive pk_live_… (browser) and sk_live_… (server). Keep the secret on your backend only.

02 / BASE URL

Point at the API

API host: https://api.koodify.ng. Checkout script: https://js.koodify.ng/v1/koodify-inline.js.

03 / INTEGRATE

Checkout and/or server APIs

Browser: koodifyInitPayment with publicKey. Server: X-Api-Key: sk_live_… for wallet, transactions, settlements, and billing.

NODE.JS · ZAINBOX WALLET
const zainboxCode = process.env.KOODIFY_ZAINBOX_CODE;

const response = await fetch(
  `https://api.koodify.ng/api/v1/zainbox/${zainboxCode}/wallet`,
  {
    headers: {
      "X-Api-Key": process.env.KOODIFY_SECRET_KEY // sk_live_…
    }
  }
);

const data = await response.json();
// data.data.balances / data.data.virtualAccounts

Authentication

You authenticate with two merchant credentials: a publishable public key for checkout, and a secret key for server APIs.

AUTH publicKey: pk_live_… / pk_test_… Browser checkout
AUTH X-Api-Key: sk_live_… / sk_test_… Server APIs
HMAC X-Koodify-Signature Webhook verification

Environments

Build with test keys (sk_test_… / pk_test_…), then switch to live keys after checkout, webhooks, and (if used) payment splits work end-to-end. Production API host: https://api.koodify.ng.

Test

Use sk_test_… / test data to validate request handling, webhooks, and error paths.

Live

Use sk_live_… and pk_live_… after your integration is verified.

API Guides

Step-by-step integration guides and request samples for the APIs you will call from your app.

Inline bank-transfer checkout

Bank transfer popup · publishable public key · temporary VA

Load https://js.koodify.ng/v1/koodify-inline.js and call koodifyInitPayment with amount, txnRef, contact details, callBackUrl, and a publishable public key. Koodify opens a session, shows a one-time virtual account, waits for the transfer, then returns status to your browser callback—and POSTs a signed payment.success event to your callBackUrl even if the tab closed.

JSkoodifyInitPayment(payload, callback)Browser
REQUEST · INLINE CHECKOUT
<script src="https://js.koodify.ng/v1/koodify-inline.js"></script>
<script>
  koodifyInitPayment({
    publicKey: "pk_live_…",
    amount: "50000",
    txnRef: "FEES_" + Date.now(),
    mobileNumber: "08012345678",
    emailAddress: "[email protected]",
    callBackUrl: "https://school.example/webhooks/koodify",
    name: "Ada Lovelace",
    customParam: { matric: "ND/12/2211", session: "2026" }
  }, function (response) {
    console.log(response.status, response.reference, response.customParam);
  });
</script>
RESPONSE · BROWSER CALLBACK
{
  "status": "success",
  "reference": "FEES_1730544000000",
  "description": "Payment was successful",
  "payer": {
    "email": "[email protected]",
    "phone": "08012345678",
    "name": "Ada Lovelace"
  },
  "customParam": {
    "matric": "ND/12/2211",
    "session": "2026"
  }
}

Virtual accounts

Create static virtual accounts with POST /api/v1/va/create and one-time / dynamic accounts with POST /api/v1/dva/create. Inline checkout also creates a temporary account for the payer. Persist the account number and your own customer reference. Optional metadata is stored by Koodify and returned to you—it is not required for the payment rail. Check balances and history with /api/v1/va/… helpers such as GET /api/v1/va/{accountNumber}.

POST/api/v1/va/createCreate static VA
REQUEST · CREATE STATIC VA
curl -X POST "https://api.koodify.ng/api/v1/va/create" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY" \
  -d '{
  "bankType": "gtBank",
  "title": "Ms",
  "firstName": "Ada",
  "surname": "Lovelace",
  "email": "[email protected]",
  "mobileNumber": "08012345678",
  "bvn": "12345678901",
  "dob": "10-12-1815",
  "gender": "F",
  "address": "12 Computing Lane, Lagos",
  "state": "Lagos",
  "zainboxCode": "main_box",
  "metadata": { "customerId": "cus_demo_001" }
}'
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "accountNumber": "5006079921",
    "accountName": "Ada Lovelace",
    "bankName": "gtBank",
    "email": "[email protected]",
    "metadata": { "customerId": "cus_demo_001" }
  }
}
POST/api/v1/dva/createCreate dynamic VA
REQUEST · CREATE DYNAMIC VA
curl -X POST "https://api.koodify.ng/api/v1/dva/create" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY" \
  -d '{
  "bankType": "gtBank",
  "email": "[email protected]",
  "amount": "5000000",
  "zainboxCode": "main_box",
  "txnRef": "ORDER_889",
  "duration": "30",
  "accountName": "School Fees",
  "callBackUrl": "https://school.example/webhooks/koodify"
}'
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "accountNumber": "9123456789",
    "txnRef": "ORDER_889"
  }
}

Payment split

After the customer pays the full amount into a primary zainbox, Koodify can redistribute value to secondary zainboxes. Pick one integration pattern per payment — you do not call both flows for the same checkout. Each share uses either a fixed amount or a percent of the remainder (percents must total 100%). The primary (collector) zainbox must have auto settlement disabled.

Two patterns — choose one
A · Ad-hoc split (server-side): send paymentSplit.shares when you initialize payment — no plan endpoint.
B · Reusable plan (browser checkout): create a plan once, then start each payment with only splitPlanId (inline checkout does not accept ad-hoc shares).

Option A — Ad-hoc split on initialize

No plan call. Your backend starts the payment and includes shares in the same request. Use this when shares change per transaction; browser checkout cannot use this path.

POST/api/v1/checkout/initializeServer-side only
REQUEST · CHECKOUT WITH AD-HOC SPLIT
curl -X POST "https://api.koodify.ng/api/v1/checkout/initialize" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY" \
  -d '{
  "zainboxCode": "main_box",
  "amount": "1000",
  "txnRef": "ORDER_889",
  "mobileNumber": "08012345678",
  "emailAddress": "[email protected]",
  "callBackUrl": "https://merchant.example/paid",
  "paymentSplit": {
    "shares": [
      { "zainboxCode": "tutor_box", "amount": "200" },
      { "zainboxCode": "agent_box", "amount": "50" },
      { "zainboxCode": "main_box", "percent": 100 }
    ]
  }
}'

Option B — Reusable plan + checkout

Call the plan API when you create or update the share recipe. For every payment after that, only start checkout with splitPlanId — do not call PUT /api/v1/split-plans again unless the plan changes.

PUT/api/v1/split-plansOnce (or when plan changes)
REQUEST · CREATE SPLIT PLAN
curl -X PUT "https://api.koodify.ng/api/v1/split-plans" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY" \
  -d '{
  "id": "split_school_fees_v1",
  "name": "School fees — tutor + agent",
  "primaryZainboxCode": "main_box",
  "isActive": true,
  "shares": [
    { "zainboxCode": "tutor_box", "amount": "200" },
    { "zainboxCode": "agent_box", "amount": "50" },
    { "zainboxCode": "main_box", "percent": 100 }
  ]
}'
BROWSER · USE PLAN ID
koodifyInitPayment({
  publicKey: "pk_live_…",
  amount: "1000",
  txnRef: "ORDER_890",
  mobileNumber: "08012345678",
  emailAddress: "[email protected]",
  callBackUrl: "https://merchant.example/paid",
  splitPlanId: "split_school_fees_v1"
}, cb);

After payment succeeds

Either pattern stores a split run for the txnRef. Inspect it anytime:

GET/api/v1/payment-splits/{txnRef}Inspect run
RESPONSE · data.paymentSplit
{
  "code": "00",
  "data": {
    "paymentSplit": {
      "txnRef": "ORDER_889",
      "primaryZainboxCode": "main_box",
      "checkoutAmount": "1000",
      "status": "success",
      "legs": [
        {
          "index": 0,
          "zainboxCode": "tutor_box",
          "role": "transfer",
          "amount": "200",
          "status": "success"
        },
        {
          "index": 1,
          "zainboxCode": "agent_box",
          "role": "transfer",
          "amount": "50",
          "status": "success"
        },
        {
          "index": 2,
          "zainboxCode": "main_box",
          "role": "keep",
          "percent": 100,
          "status": "success"
        }
      ]
    }
  }
}

Webhooks

Signed events · payment.success · verify on raw body

On payment success, Koodify POSTs a signed JSON event to the callBackUrl you set at checkout—even if the browser tab closed. Verify X-Koodify-Signature (hex HMAC-SHA256 of the raw body) using your webhook signing secret, respond with HTTP 2xx quickly, and process the event asynchronously. Duplicate deliveries are safe to ignore when you key on reference.

POST{your callBackUrl}Merchant endpoint
HEADERS · DELIVERY
POST /webhooks/koodify/ HTTP/1.1
Content-Type: application/json
X-Koodify-Event: payment.success
X-Koodify-Signature: <hex hmac-sha256 of raw body>
PAYLOAD · payment.success
{
  "event": "payment.success",
  "status": "success",
  "reference": "ORDER_889",
  "description": "Payment was successful",
  "amount": "1000",
  "currencyCode": "NGN",
  "zainboxCode": "main_box",
  "payer": {
    "email": "[email protected]",
    "phone": "08012345678",
    "name": "Ada Lovelace"
  },
  "customParam": {
    "matric": "ND/12/2211",
    "session": "2026"
  },
  "paymentSplit": {
    "txnRef": "ORDER_889",
    "status": "success",
    "legs": []
  }
}
VERIFY · SIGNATURE
// expected = HMAC_SHA256_hex(your_webhook_secret, raw_body)
// compare expected to header X-Koodify-Signature (constant-time)
// return 200 OK, then mark the order paid using event.reference

API endpoints

Stable /api/v1 routes for integration. Checkout, VAs, splits, and webhooks are covered in the guides above; samples below cover transfers, settlements, and reading your zainbox.

Accept payments

How-to walkthroughs live in Guides. This catalogue lists the stable routes those guides use—without repeating the same samples.

Browser popup via koodifyInitPayment, or server initialize.

POST/api/v1/checkout/initializeServer checkout
POST/api/v1/checkout/initialize/inlineInline session
GET/api/v1/checkout/accountTemporary VA
GET/api/v1/checkout/statusPayment status

Static and dynamic VA create flows, with optional metadata.

POST/api/v1/va/createCreate static VA
GET/api/v1/va/{accountNumber}Balance + transactions
PATCH/api/v1/va/statusUpdate VA status
POST/api/v1/dva/createCreate dynamic VA
GET/api/v1/dva/verify/{txnRef}Status + deposit verify
GET/api/v1/dva/deposit/{txnRef}Deposit status

Ad-hoc paymentSplit on initialize, or reusable plans with splitPlanId on checkout.

PUT/api/v1/split-plansCreate / update plan
GET/api/v1/split-plansList plans
GET/api/v1/split-plans/{id}Get one plan
GET/api/v1/payment-splits/{txnRef}Inspect split run

Transfers

List banks, confirm account names, send funds, and verify the transfer.

GET/api/v1/banksBank list

List supported banks and codes for transfers and name enquiry.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/banks" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": [
    {
      "code": "058",
      "name": "Guaranty Trust Bank"
    },
    {
      "code": "50515",
      "name": "Moniepoint MFB"
    }
  ]
}
GET/api/v1/banks/enquiryName enquiry

Resolve the account name for a bank code + account number.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/banks/enquiry?bankCode=058&accountNumber=0123456789" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "accountName": "ADA LOVELACE",
    "accountNumber": "0123456789",
    "bankCode": "058"
  }
}
POST/api/v1/transfersFunds transfer

Initiate a single funds transfer (verify separately if needed).

REQUEST · CURL
curl -X POST "https://api.koodify.ng/api/v1/transfers" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY" \
  -d '{
  "destinationAccountNumber": "0123456789",
  "destinationBankCode": "058",
  "sourceAccountNumber": "5006079921",
  "amount": "5000",
  "txnRef": "PAYOUT_1001",
  "zainboxCode": "main_box",
  "narration": "Tutor payout"
}'
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "txnRef": "PAYOUT_1001",
    "status": "pending"
  }
}
GET/api/v1/transfers/{txnRef}/verifyVerify transfer

Verify a transfer by its txnRef.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/transfers/ORDER_889/verify" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "txnRef": "PAYOUT_1001",
    "status": "success",
    "amount": "5000"
  }
}

Settlements

Configure settlement behaviour and inspect settlement accounts for a zainbox.

POST/api/v1/settlementsCreate settlement

Create or update settlement configuration for a zainbox. Use GET /api/v1/settlements to read current settings.

REQUEST · CURL
curl -X POST "https://api.koodify.ng/api/v1/settlements" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY" \
  -d '{
  "name": "Main settlement",
  "scheduleType": "T1",
  "zainboxCode": "main_box",
  "status": false,
  "settlementAccountList": [
    {
      "accountNumber": "0123456789",
      "bankCode": "058",
      "accountName": "SCHOOL LTD"
    }
  ]
}'
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "zainboxCode": "main_box",
    "status": false
  }
}
GET/api/v1/settlementsGet settlement

Read settlement configuration (pass zainbox query as required by your integration).

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/settlements?zainboxCode=main_box" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "zainboxCode": "main_box",
    "status": false,
    "scheduleType": "T1"
  }
}
GET/api/v1/settlements/history/{zainboxCode}History

Settlement history for a zainbox.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/settlements/history/main_box" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": [
    {
      "amount": "50000",
      "status": "success",
      "settledAt": "2026-11-01T10:00:00Z"
    }
  ]
}
GET/api/v1/settlements/internal/{zainboxCode}Settlement account

Fetch the settlement account for a zainbox.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/settlements/internal/main_box" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "accountNumber": "1100001234",
    "bankName": "Access Bank"
  }
}

Transactions

Confirm payments, look up activity, and read your zainbox wallet and transactions.

Prefer the signed payment.success webhook. Use deposit verify when you need a server-side status check.

POST{your callBackUrl}payment.success webhook
GET/api/v1/reconcile/deposit/{txnRef}Verify deposit
GET/api/v1/activity/{txnRef}Payment timeline

Koodify activity events for one payment reference (initialize, VA create, status, webhook).

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/activity/ORDER_889" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": [
    {
      "type": "checkout.initialize",
      "txnRef": "ORDER_889",
      "at": "2026-11-02T10:00:00Z"
    },
    {
      "type": "payment.success",
      "txnRef": "ORDER_889",
      "at": "2026-11-02T10:04:12Z"
    }
  ]
}
GET/api/v1/activityRecent activity

Recent activity for your account.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/activity" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": [
    {
      "type": "payment.success",
      "txnRef": "ORDER_889",
      "at": "2026-11-02T10:04:12Z"
    }
  ]
}
GET/api/v1/transactionsSummary + list

Transactions summary + list for your account. Also available as /api/v1/transactions/summary and /list.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/transactions" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "summary": {
      "count": 12,
      "volume": "450000"
    },
    "transactions": [
      {
        "txnRef": "ORDER_889",
        "amount": "1000",
        "status": "success"
      }
    ]
  }
}
GET/api/v1/zainbox/{zainboxCode}Profile + balances

Zainbox profile plus balances.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/zainbox/main_box" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "profile": {
      "codeName": "main_box",
      "name": "Main box"
    },
    "balances": [
      {
        "accountNumber": "5006079921",
        "balance": "125000.00"
      }
    ]
  }
}
GET/api/v1/zainboxesList zainboxes

List zainboxes for the authenticated merchant.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/zainboxes" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": [
    {
      "codeName": "main_box",
      "name": "Main box"
    }
  ]
}
GET/api/v1/zainbox/{zainboxCode}/walletVAs + balances

Virtual accounts and balances for a zainbox—the usual read after checkout goes live.

REQUEST · CURL
curl -X GET "https://api.koodify.ng/api/v1/zainbox/main_box/wallet" \
  -H "X-Api-Key: $KOODIFY_SECRET_KEY"
RESPONSE · 200 OK
{
  "code": "00",
  "status": "200 OK",
  "description": "successful",
  "data": {
    "virtualAccounts": [
      {
        "accountNumber": "5006079921"
      }
    ],
    "balances": [
      {
        "accountNumber": "5006079921",
        "balance": "125000.00"
      }
    ]
  }
}

Error handling

API responses use a standard envelope: code, status, description, and data. Treat a payment as successful only when the payment status is explicitly successful (for example success or paid). Reused txnRef values fail checkout—issue a fresh reference for each attempt. On webhooks, verify the signature, acknowledge with 2xx, and ignore duplicates for the same reference.

Security best practices

Keep sk_… on the server only; publish only pk_… in the browser. Verify X-Koodify-Signature on the raw webhook body. Never log API keys, BVN, or other sensitive customer data. Use an HTTPS callBackUrl that accepts POST.

Ready to build something reliable?

Call your zainbox with sk_…, embed inline checkout with pk_…, and verify webhooks before going live.

Go to quickstart →