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

  1. Log into the NadaPay Dashboard
  2. Navigate to Settings → API Keys
  3. Click Generate Key for the environment you need
  4. Copy the key — it is only shown once

Your key format:

EnvironmentPrefix
Sandboxnpk_sandbox_
Productionnpk_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 uses x-api-key — not Authorization: Bearer. Using the wrong header returns 401 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

StatusMeaningCan transact?
pendingVerification not yet submitted
under_reviewDocuments submitted, review in progress
verifiedVerification passed
rejectedVerification failed — see rejection reason
suspendedAccount 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 exact reference provided. 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.