Appearance
Collect by bank transfer
Let a customer pay from their bank account. Payd gives you bank details for the payment: you show them to the customer, they send a transfer from their bank app or branch, and Payd confirms it.
POST
https://api.payd.money/api/v2/payments/payinHow it works
- You look up the banks for the customer's country with payment options by country.
- The customer picks their bank, and you send the payin with that bank's ids.
- Payd answers with
bank_instructions: the bank, the account number, the account name, the reference and the exact amount to send. - You show those details to the customer. They send the transfer before
expires_at. - Payd posts the result to your
callback_url, and the status endpoint showssuccessorfailed.
Where you can collect
| Country | country | currency | Banks today | Limits per payment |
|---|---|---|---|---|
| Kenya | KE | KES | 44 | KES 301 to 999,999 |
| Nigeria | NG | NGN | 23 | NGN 2,501 to 30,000,000 |
| Tanzania | TZ | TZS | 35 | TZS 2,501 to 150,000,000 |
| Botswana | BW | BWP | 9 | from BWP 151 |
These limits are a snapshot from 16 October 2026. The API is the source of truth: read each bank's min_amount, max_amount and settlement_seconds from payment options by country before each payment. See limits and settlement.
1. Pick the bank
Fetch the options for the customer's country and keep the banks for collections:
bash
curl https://api.payd.money/api/v2/payments/discovery/KE \
-u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD"Keep networks where channel_type is bank, ramp_type is deposit and status is active. Show network_name to the customer, with a search box, since some countries list dozens of banks. When they choose one, keep its network_id, channel_id, min_amount and max_amount.
2. Send the payin
bash
curl https://api.payd.money/api/v2/payments/payin \
-u "$PAYD_API_USERNAME:$PAYD_API_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "order-8842",
"amount": 25000,
"currency": "KES",
"country": "KE",
"transaction_channel": "bank",
"customer": {
"first_name": "Wanjiru",
"last_name": "Kamau",
"phone_number": "+254712345678",
"email": "wanjiru@example.com",
"country": "KE"
},
"vendor_config": { "account_id": "4f28836c-2f1e-4d9b-9a51-6f3c2b1d8e70" },
"provider_config": {
"network_id": "3f1c2a8e-5b7d-4e19-9c0a-2d6b8e4f1a73",
"channel_id": "b8d2e6f4-1a3c-4b5d-8e7f-9a0b1c2d3e4f",
"account_type": "bank"
},
"payment_config": {
"account_number": "+254712345678",
"remark": "Order 8842",
"reference": "order-8842",
"callback_url": "https://example.com/webhooks/payd"
}
}'js
const auth = Buffer.from(
`${process.env.PAYD_API_USERNAME}:${process.env.PAYD_API_PASSWORD}`,
).toString('base64')
const headers = { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json' }
// 1. Banks the customer can pay from in Kenya
const options = await fetch('https://api.payd.money/api/v2/payments/discovery/KE', { headers })
.then((r) => r.json())
const banks = Object.values(options.provider_networks ?? {})
.flatMap((group) => group.networks)
.filter((n) => n.channel_type === 'bank' && n.ramp_type === 'deposit' && n.status === 'active')
// 2. The customer picked one
const bank = banks.find((n) => n.network_name === customerChoice)
const res = await fetch('https://api.payd.money/api/v2/payments/payin', {
method: 'POST',
headers,
body: JSON.stringify({
idempotency_key: 'order-8842',
amount: 25000,
currency: 'KES',
country: 'KE',
transaction_channel: 'bank',
customer: { first_name: 'Wanjiru', last_name: 'Kamau', phone_number: '+254712345678', country: 'KE' },
vendor_config: { account_id: process.env.PAYD_ACCOUNT_ID },
provider_config: { network_id: bank.network_id, channel_id: bank.channel_id, account_type: 'bank' },
payment_config: {
account_number: '+254712345678',
reference: 'order-8842',
callback_url: 'https://example.com/webhooks/payd',
},
}),
signal: AbortSignal.timeout(35_000),
})
const body = await res.json()
if (!res.ok) throw new Error(`${body.error_code}: ${body.error_message}`)
// 3. Show these to the customer
const { bank_instructions: pay, merchant_reference } = body.gateway_response
console.log(merchant_reference, pay.bank_name, pay.account_number, pay.account_name, pay.reference, pay.amount, pay.expires_at)python
import os
import requests
AUTH = (os.environ["PAYD_API_USERNAME"], os.environ["PAYD_API_PASSWORD"])
# 1. Banks the customer can pay from in Kenya
options = requests.get("https://api.payd.money/api/v2/payments/discovery/KE", auth=AUTH, timeout=15).json()
banks = [
n
for group in (options.get("provider_networks") or {}).values()
for n in group["networks"]
if n["channel_type"] == "bank" and n["ramp_type"] == "deposit" and n["status"] == "active"
]
# 2. The customer picked one
bank = next(n for n in banks if n["network_name"] == customer_choice)
res = requests.post(
"https://api.payd.money/api/v2/payments/payin",
auth=AUTH,
json={
"idempotency_key": "order-8842",
"amount": 25000,
"currency": "KES",
"country": "KE",
"transaction_channel": "bank",
"customer": {"first_name": "Wanjiru", "last_name": "Kamau", "phone_number": "+254712345678", "country": "KE"},
"vendor_config": {"account_id": os.environ["PAYD_ACCOUNT_ID"]},
"provider_config": {"network_id": bank["network_id"], "channel_id": bank["channel_id"], "account_type": "bank"},
"payment_config": {
"account_number": "+254712345678",
"reference": "order-8842",
"callback_url": "https://example.com/webhooks/payd",
},
},
timeout=35,
)
res.raise_for_status()
# 3. Show these to the customer
gateway = res.json()["gateway_response"]
pay = gateway["bank_instructions"]
print(gateway["merchant_reference"], pay["bank_name"], pay["account_number"], pay["reference"], pay["amount"], pay["expires_at"])Fields
| Field | Type | Required | Description |
|---|---|---|---|
idempotency_key | string | No | Makes a resend safe. See retries and duplicates. |
amount | number | Yes | Amount in the local currency, as a whole number, within the bank's limits. |
currency | string | Yes | The country's currency from the table above. |
country | string | Yes | The two-letter country code. |
transaction_channel | string | Yes | bank. |
customer.first_name, customer.last_name | string | Yes | The customer's name, as their bank knows it. |
customer.phone_number | string | Yes | The customer's phone number in international form with a +. |
customer.email | string | No | The customer's email. |
customer.country | string | No | The same country code. |
customer.address | object | No | line_1, city, state, postal_code and country. Payd fills a default for the country when you leave it out. |
customer.additional_document_type | string | Nigeria | BVN or NIN. |
customer.additional_document_number | string | Nigeria | The customer's BVN or NIN. |
vendor_config.account_id | string | Yes | Your account id. |
provider_config.network_id | string | Yes | network_id of the bank the customer chose. |
provider_config.channel_id | string | Yes | channel_id of that bank, from its deposit entry. |
provider_config.account_type | string | Yes | bank. |
payment_config.account_number | string | Yes | The customer's phone number again, in the same form. |
payment_config.reference | string | No | Your own id for the payment. |
payment_config.callback_url | string | No | Where Payd posts the result. See webhooks. |
payment_config.remark | string | No | A short note kept with the payment. |
Nigeria
Nigerian banks need to know who is paying. Send the customer's BVN or NIN in customer.additional_document_type and customer.additional_document_number. Without them, Payd refuses the payin with a message that names additional_id_type.
Response
json
{
"gateway_response": {
"success": true,
"merchant_reference": "9BD103350517eR",
"checkout_request_id": "c41f9a0e-7b2d-4c8e-a1f3-5d6e7f8a9b0c",
"message": "request sent",
"bank_instructions": {
"bank_name": "Equity Bank",
"account_number": "1180289374615",
"account_name": "Payd Collections",
"branch_code": "",
"reference": "PD7Q4MZ2K8",
"amount": 25000,
"currency": "KES",
"expires_at": "2026-10-17T11:42:03Z",
"checkout_url": ""
}
}
}| Field | Meaning |
|---|---|
merchant_reference | Payd's reference for the payment. Store it, and use it to check the status. |
bank_instructions.bank_name | The bank the customer sends money to. |
bank_instructions.account_number | The account to pay. It can be a one-off account made for this payment, so never reuse it for another order. |
bank_instructions.account_name | The name on that account. |
bank_instructions.branch_code | The branch code, where the bank needs one. Often empty. |
bank_instructions.reference | The reference the customer must put on the transfer. Show it clearly. |
bank_instructions.amount, currency | The exact amount to send. |
bank_instructions.expires_at | Pay before this time. After it, the details stop working. |
bank_instructions.checkout_url | When set, send the customer to this page to pay instead of showing account details. |
If success is false or bank_instructions is missing, there is nothing to show. Log message and let the customer try again or pick another way to pay.
Show the details
Show every field the customer needs on one screen, with a copy button next to the account number and the reference:
- Ask them to send exactly
amount. Payd credits the amount of the payin, so a short or extra transfer needs support to sort out. - Ask them to use the reference, or the bank cannot match the transfer to the payment.
- Show
expires_atin their time zone, and tell them to pay before then.
Confirm the payment
Bank transfers are not instant. Payd checks for the transfer and posts payin.succeeded or payin.failed to your callback_url when it knows. See webhooks.
- Most transfers confirm within minutes. Some Kenyan banks take up to two days. See limits and settlement.
- If no webhook arrives, check the status every few minutes for the first hour, then every hour.
- If the payment is still
pendingafterexpires_at, treat it as unpaid and ask the customer before you try again.
Common errors
error_code | Message | Fix |
|---|---|---|
invalid_argument | names a missing field | Send the field. provider_config.network_id, channel_id and account_type are all needed. |
invalid_argument or provider_error | names additional_id_type | Send the customer's BVN or NIN. See Nigeria. |
provider_error | the network's reason | Usually an amount outside the bank's limits. Check min_amount and max_amount. |
resource_not_found | no pricing configured... | Payd does not offer bank collections in this country and currency. Check the options for the country. |
See errors for every code.

