Guide
Collect a payment
Give each payer a one-time bank account for exactly what they owe, and get a signed webhook the moment they pay.
Want Lint to show the payer the account details, renew them if they lapse, and confirm the payment on screen? Use hosted checkout instead. This guide is for showing the details in your own UI.
A one-time virtual account is a real Nigerian bank account number that exists for a single payment. Your customer pays it from any bank app; Lint matches the money to your order and tells you. There is no card form, no redirect and nothing for the payer to install.
The flow
- Your server asks Lint for a virtual account for the order's amount.
- You show the payer the account number, bank name and exact amount.
- The payer sends a bank transfer.
- Lint posts
virtual_account.payment_receivedto yourcallback_url. - You verify the webhook, confirm with the API, and fulfil the order.
Create the account
curl -X POST https://staging.lint.finance/api/partner/v1/payments/virtual-accounts/create \
-H "Authorization: Bearer $LINT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"amount": 1250000,
"currency": "NGN",
"reference": "order_8812",
"callback_url": "https://api.yourcompany.com/lint/webhooks",
"validity": 1800,
"amount_control": "FIXED"
}'| Field | Notes |
|---|---|
amount | Required. What you expect, in kobo: 1250000₦12,500.00. |
currency | Required. NGN. |
callback_url | Required. Where the payment webhook for this account goes. |
reference | Your order ID. Must be unique across your virtual accounts; comes back as external_reference in the webhook. Send one — it is how you match the payment to the order. |
validity | Seconds the account accepts payment, up to 86400 (24 hours). Defaults to 30 minutes. |
amount_control | FIXED (default), ALLOW_UNDERPAYMENT or ALLOW_OVERPAYMENT — how a transfer that doesn't match amount is treated. |
The response gives you everything to show the payer:
{
"data": {
"id": "517cadd0-3d20-435e-8187-72fe9c1b2a40",
"payment_status": "PENDING",
"account_number": "6020012029",
"bank_name": "SAFE HAVEN SANDBOX BANK",
"bank_code": "999240",
"account_name": "Your Business Checkout",
"reference": "order_8812",
"amount": 1250000,
"amount_paid": 0,
"currency": "NGN",
"valid_until": "2026-11-02T10:34:00.000000Z",
"amount_control": "FIXED",
"callback_url": "https://api.yourcompany.com/lint/webhooks",
"transaction": null
},
"message": "Virtual account created successfully"
}Store id against your order. Show the payer account_number, bank_name, account_name and the amount in naira, and tell them the account expires at valid_until.
Receive the payment
When the transfer lands, Lint sends virtual_account.payment_received to the account's callback_url, signed like every webhook:
{
"event": "virtual_account.payment_received",
"data": {
"transaction_status": "SUCCESSFUL",
"amount": 1250000,
"currency": "NGN",
"fees": 0,
"transaction_reference": "b7d7eaf3db594caeb95b4ef9b46e38341785231752",
"external_reference": "order_8812",
"virtual_account": {
"account_number": "6020012029",
"account_name": "Your Business Checkout",
"bank_code": "999240",
"bank_name": "SAFE HAVEN SANDBOX BANK"
},
"created_at": "2026-11-02T10:12:40+00:00"
}
}Then:
- Verify the signature and reply
200straight away. - Look up your order by
external_reference. - Compare
amountwith what you expected. WithALLOW_UNDERPAYMENTorALLOW_OVERPAYMENTthe two can differ, so decide what each case means for your order. - Mark the order paid, once — the same event may be delivered again, so make this step idempotent on
Lint-Delivery-Idor on the order.
Confirm with the API
The webhook is a prompt; the API is the record. Fetch the account to confirm, or to recover if you missed a webhook:
curl https://staging.lint.finance/api/partner/v1/payments/virtual-accounts/517cadd0-3d20-435e-8187-72fe9c1b2a40/payment-status \
-H "Authorization: Bearer $LINT_ACCESS_TOKEN" \
-H "Accept: application/json"payment_status moves from PENDING to the payment's status, amount_paid shows what arrived in kobo, and transaction holds the full transaction.
Test it in the sandbox
POST /payments/virtual-accounts/{id}/simulate-transfer pays into a sandbox account as a real transfer would and sends the webhook. Try the full amount, then a short and an excess payment against each amount_control, and send the same webhook twice to check you only fulfil once.
Each account takes one payment. For a payer who will pay you repeatedly, open a customer wallet instead: it keeps the same account number for good.