Authenticate with x-api-key
All NadaPay API requests are authenticated using the x-api-key header. This is the only authentication method supported — there are no OAuth flows or bearer token exchanges required for server-to-server integration.
Step 1 — Get your API key
- Log into the NadaPay Dashboard
- Navigate to Settings → API Keys
- Click Generate Key for the environment you need
- Copy the key — it is only shown once
Your key format:
| Environment | Prefix |
|---|---|
| Sandbox | npk_sandbox_ |
| Production | npk_live_ |
Step 2 — Add the header to every request
curl --request GET \
--url https://sandbox.api.nadapay.com/v1/accounts \
--header 'x-api-key: npk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxx'That's it. Every request to the NadaPay API requires this header.
Step 3 — Verify it's working
curl --request GET \
--url https://sandbox.api.nadapay.com/v1/ping \
--header 'x-api-key: YOUR_API_KEY'Expected response:
{ "status": "ok", "environment": "sandbox" }
NadaPay usesx-api-key— notAuthorization: Bearer. Using the wrong header returns401 Unauthorized.
Fetch Organization Accounts
Your organization's accounts are the entry point for checking balances and initiating transactions. This guide shows you how to list all accounts and fetch a specific one by ID.
Step 1 — List all accounts
curl --request GET \
--url https://sandbox.api.nadapay.com/v1/accounts \
--header 'x-api-key: YOUR_API_KEY'Response:
{
"data": [
{
"id": "acct_01HXXXXXXXXXXXXXXXXXX",
"label": "Main Operations",
"status": "active",
"created_at": "2025-01-01T00:00:00Z"
},
{
"id": "acct_01HYYYYYYYYYYYYYYYYYY",
"label": "Vendor Payouts",
"status": "active",
"created_at": "2025-01-01T00:00:00Z"
}
]
}Step 2 — Fetch a specific account by ID
curl --request GET \
--url https://sandbox.api.nadapay.com/v1/accounts/acct_01HXXXXXXXXXXXXXXXXXX \
--header 'x-api-key: YOUR_API_KEY'Step 3 — Check wallet balances on the account
curl --request GET \
--url https://sandbox.api.nadapay.com/v1/wallets?account_id=acct_01HXXXXXXXXXXXXXXXXXX \
--header 'x-api-key: YOUR_API_KEY'Use the available_balance field to determine if a wallet has sufficient funds for a transaction.
See Accounts and Balances for a full breakdown of balance types.
Check Verification Status
Before your organization can transact in production, it must pass KYC/KYB verification. This guide shows you how to check the current verification status programmatically.
Step 1 — Fetch verification status
curl --request GET \
--url https://sandbox.api.nadapay.com/v1/organization/verification \
--header 'x-api-key: YOUR_API_KEY'Response:
{
"data": {
"status": "verified",
"type": "kyb",
"verified_at": "2025-01-01T00:00:00Z",
"details": {
"business_name": "Acme Corp",
"country": "NG"
}
}
}Step 2 — Understand the status values
| Status | Meaning | Can transact? |
|---|---|---|
pending | Verification not yet submitted | ❌ |
under_review | Documents submitted, review in progress | ❌ |
verified | Verification passed | ✅ |
rejected | Verification failed — see rejection reason | ❌ |
suspended | Account suspended by compliance team | ❌ |
Step 3 — Listen for the verification webhook
Rather than polling, listen for the organization.verified event:
{
"event": "organization.verified",
"data": {
"organization_id": "org_01HXXXXXXXXXXXXXXXXXX",
"status": "verified",
"verified_at": "2025-01-01T00:00:00Z"
}
}
In the sandbox, organizations are automatically verified. See Environments and Base URLs for sandbox behaviour.
Create a Beneficiary
A beneficiary is a named, reusable payout destination. Create one before adding their specific bank account or mobile money details.
Step 1 — Create the beneficiary
curl --request POST \
--url https://sandbox.api.nadapay.com/v1/beneficiaries \
--header 'x-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--header 'x-idempotency-Key: YOUR_UNIQUE_UUID' \
--data '{
"name": "John Doe",
"type": "individual",
"currency": "NGN",
"country": "NG",
"email": "[email protected]"
}'Response:
{
"data": {
"id": "ben_01HXXXXXXXXXXXXXXXXXX",
"name": "John Doe",
"type": "individual",
"status": "active",
"created_at": "2025-01-01T00:00:00Z"
}
}Save the id — you will use it to add a beneficiary account in the next step.
Step 2 — Add a beneficiary account
curl --request POST \
--url https://sandbox.api.nadapay.com/v1/beneficiaries/ben_01HXXXXXXXXXXXXXXXXXX/accounts \
--header 'x-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: YOUR_UNIQUE_UUID' \
--data '{
"type": "bank_account",
"account_number": "0123456789",
"bank_code": "058",
"currency": "NGN",
"country": "NG"
}'
See Add Beneficiary Account for all supported account types and required fields by country.
Resolve a Bank Account
Before adding a bank account to a beneficiary, use the Resolve Bank Account endpoint to verify the account exists and retrieve the account holder name. This prevents failed payouts due to incorrect details.
Step 1 — Resolve the bank account
curl --request POST \
--url https://sandbox.api.nadapay.com/v1/payments/resolve-bank-account \
--header 'x-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"account_number": "0123456789",
"bank_code": "058",
"country": "NG",
"currency": "NGN"
}'Response — resolved:
{
"data": {
"account_number": "0123456789",
"account_name": "John Doe",
"bank_name": "GTBank",
"bank_code": "058",
"status": "valid"
}
}Response — not found:
{
"error": {
"code": "bank_account_not_found",
"message": "The account number could not be verified with the specified bank.",
"request_id": "req_01HXXXXXXXXXXXXXXXXXX"
}
}Step 2 — Use the resolved name for confirmation
Before creating the beneficiary account, show the resolved account_name to the user or operations team and ask them to confirm it matches the intended recipient. This is a critical step to prevent misdirected transfers.
Step 3 — Add the validated account to the beneficiary
Once confirmed, add it using Add Beneficiary Account.
Always resolve before adding. Never skip this step for bank account payouts.
Get Deposit Instructions
To fund a wallet in production, you need NadaPay-provided deposit instructions – the bank details your team or clients use to send an inbound wire or local transfer.
Step 1 — Fetch deposit instructions for a wallet
curl --request POST \
--url https://sandbox.api.nadapay.com/v1/payments/deposit-instructions \
--header 'x-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"wallet_id": "wlt_01HXXXXXXXXXXXXXXXXXX",
"currency": "USD"
}'Response:
{
"data": {
"wallet_id": "wlt_01HXXXXXXXXXXXXXXXXXX",
"currency": "USD",
"instructions": {
"bank_name": "Example Bank",
"account_name": "NadaPay Ltd — Acme Corp",
"account_number": "000123456789",
"routing_number": "021000021",
"reference": "NADAPAY-ORG-XXXX",
"payment_method": "wire"
}
}
}Step 2 — Share instructions with the funding party
Provide the full instruction set — including the reference — to whoever is initiating the inbound transfer. The reference is critical: it is how NadaPay matches the inbound payment to your wallet.
Step 3 — Wait for the wallet-funded webhook
Once funds arrive, NadaPay credits your wallet and delivers a wallet.funded event. Do not begin transactions until this event is received and the available_balance is confirmed.
Deposit instructions are unique per wallet and must include the exactreferenceprovided. Missing or incorrect references will delay crediting.
Fetch Transaction Limits
Every corridor has minimum and maximum transaction limits. Fetch these before building your UI or initiating high-value transactions to avoid limit errors at execution time.
Step 1 — Fetch limits for a currency and country
curl --request POST \
--url https://sandbox.api.nadapay.com/v1/payments/transaction-limits \
--header 'x-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"currency": "NGN",
"country": "NG"
}'Response:
{
"data": {
"currency": "NGN",
"country": "NG",
"limits": {
"minimum_amount": 1000,
"maximum_amount": 50000000,
"daily_limit": 200000000,
"currency": "NGN"
}
}
}Step 2 — Validate the transaction amount against limits
Before executing a transaction, confirm:
transaction_amount >= minimum_amount
transaction_amount <= maximum_amount
daily_volume + transaction_amount <= daily_limit
Surface a clear error to users if their amount falls outside these bounds, rather than letting the transaction fail at execution.
Limits can change. Fetch them fresh per session rather than hardcoding them in your application.
Get Provider Networks for a Country
Provider networks are the available payout rails for a specific country — for example, local bank transfer, mobile money providers, or real-time payment schemes. Fetch these to know which payout methods are available before creating a beneficiary account.
Step 1 — Fetch provider networks for a country
curl --request POST \
--url https://sandbox.api.nadapay.com/v1/payments/provider-networks \
--header 'x-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"country": "GH"
}'Response:
{
"data": {
"country": "GH",
"networks": [
{
"code": "bank_transfer",
"name": "Local Bank Transfer",
"currency": "GHS",
"type": "bank_account"
},
{
"code": "mtn_momo",
"name": "MTN Mobile Money",
"currency": "GHS",
"type": "mobile_money"
},
{
"code": "vodafone_cash",
"name": "Vodafone Cash",
"currency": "GHS",
"type": "mobile_money"
}
]
}
}Step 2 — Use the network code when adding a beneficiary account
The code returned here maps to the network field when you add a beneficiary account:
--data '{
"type": "mobile_money",
"network": "mtn_momo",
"phone_number": "+233201234567",
"currency": "GHS",
"country": "GH"
}'
Network availability varies by corridor. Always fetch provider networks dynamically rather than hardcoding them.
Updated about 2 hours ago