Events and payloads
A webhook subscription is an HTTPS endpoint you register with the bank, a list of event types you want, and a signing secret. When something happens on an account you can reach, BankCore writes a delivery row and a poller posts it to you within about ten seconds. This page is about what arrives. Verifying signatures covers proving it came from us, and Delivery and retries covers what happens when your endpoint does not answer.
Subscriptions are created by the bank against your partner API client. You tell your relationship manager the URL, the event types and any filters; the secret is handed to you once, out of band.
What a partner receives
Section titled “What a partner receives”Every event is matched against every active subscription in the tenant, then filtered. For a partner client the filter that matters most is the account scope: the event must name an account, and that account must be one you can reach. That is the same test the API applies on every call:
- your settlement account,
- any account of the corporate customer your client acts for,
- any account of a sub-agent you onboarded, provided
sub_agent_access_enabledis on for your client.
An event that carries no account at all is dropped for a partner subscription before anything else is looked at. In practice that means only these types can ever reach you:
| Event | What it is | Account on the event |
|---|---|---|
transaction.completed |
A posted transaction on an account you reach: wallet funding, transfers in or out, NIP in or out, virtual account credits, bill and airtime debits, fees. One event per leg, so a wallet-to-wallet transfer between two of your accounts arrives twice (TRANSFER_OUT, TRANSFER_IN). |
Primary account of the leg |
transaction.reversed |
A posted transaction was reversed, for example a NIP transfer the receiving bank rejected. Carries the original reference and the reversal reference. | Primary account of the leg |
bill.payment.completed |
A bill or airtime payment reached the provider. Carries the vend receipt (token, units, receipt). |
Payer account |
bill.payment.failed |
The provider refused the payment. | Payer account |
campaign.award.paid |
A promotional award the bank paid to a customer you created. Addressed to the client that created the customer, nobody else. | Award account |
card.authorization, card.reversed |
Card activity on an account you reach, if you hold virtual cards. | Cardholder account |
webhook.test |
Fired by the bank to prove your endpoint. Bypasses every filter. | None (exempt) |
Types such as customer.created, agent.profile.created, account.created, kyc.* and nip.transfer.* exist in the catalogue and can be listed on a subscription, but they do not carry an account, so they are filtered out for partner clients. Subscribe to them if you like; do not wait for them.
Two further filters can be set on your subscription by the bank: a customer type list (INDIVIDUAL, CORPORATE, AGENT) and a source channel list. Ask for them if you only want, say, sub-agent events.
The envelope
Section titled “The envelope”The body is compact JSON, UTF-8, no whitespace. Every payload carries type and eventId. BankCore’s own identifiers (tenantId, customerId, accountId) are stripped before sending; reconcile on account numbers and references.
| Field | Present on | Meaning |
|---|---|---|
type |
all | The event type, same value as the X-Webhook-Event header. |
eventId |
all | The bank’s id for the event. Your idempotency key. |
timestamp |
all | When the event was raised inside BankCore, before queueing. |
reference |
transaction, bill | BankCore’s transaction reference. |
clientReference |
transaction | The transactionRef / external_ref you supplied on the originating call, when there was one. |
txType |
transaction | See Field dictionaries. |
amount, currency |
transaction, bill, campaign | Amount of this leg. For a virtual account credit this is the net amount posted; grossAmount and feeAmount carry the split. |
sourceAccountNumber, destinationAccountNumber |
transaction | Either may be null: a NIP inward credit has no source account, a NIP outward debit has no destination account. |
accountNumber, accountName, customerName, customerPhone |
transaction, campaign | The account this event is about and who owns it. |
customerType, sourceChannel |
transaction, campaign | Owner type and the channel that originated the transaction. |
reversalReference, reversalReason |
transaction.reversed |
The reversing transaction and why. |
Then, for every transaction event, the transaction’s metadata is merged into the top level. The keys depend on the kind of money movement and are listed at the end of this page.
Samples
Section titled “Samples”{"type":"transaction.completed","eventId":"9f3b2c14-7a6e-4d51-b0c8-2e5a91d47f60","reference":"TXN20260901084512001","clientReference":"INV-8842190","txType":"NIP_OUTWARD","amount":50000.00,"currency":"NGN","sourceAccountNumber":"6020012345","destinationAccountNumber":null,"customerType":"AGENT","sourceChannel":"API","accountNumber":"6020012345","accountName":"ADEOLA STORES","customerName":"ADEOLA STORES","customerPhone":"08012345678","beneficiaryName":"CHINEDU OKAFOR","beneficiaryAccount":"0123456789","beneficiaryBank":"000013","beneficiaryBankCbnCode":"058","beneficiaryBankName":"GTBank","nipSessionId":"000013260901084512123456789012","senderName":"ADEOLA STORES","senderAccount":"6020012345","feeAmount":10.75,"vatAmount":0.81,"stampDutyAmount":50.00}The fee, VAT and stamp duty are separate transactions on the same account (txType FEE, TAX, STAMP_DUTY) and arrive as their own transaction.completed events, with references <reference>-FEE, -VAT and -SD.
{"type":"transaction.completed","eventId":"2c0e8f7a-1b3d-4e5f-9a6b-7c8d9e0f1a2b","reference":"NIN20260901091533ABCDEF","clientReference":"ORDER-1234","txType":"NIP_INWARD","amount":48500.00,"currency":"NGN","sourceAccountNumber":null,"destinationAccountNumber":"6020012345","customerType":"AGENT","sourceChannel":"SYSTEM","accountNumber":"6020012345","accountName":"ADEOLA STORES","customerName":"ADEOLA STORES","customerPhone":"08012345678","senderName":"JOHN DOE","senderAccount":"0123456789","senderBank":"000014","senderBankName":"Access Bank","nipSessionId":"000014260901091533000000000001","narration":"Payment for order 1234","virtualAccountNumber":"4000001042","virtualAccountName":"ADEOLA STORES","externalRef":"ORDER-1234","beneficiaryAccount":"6020012345","beneficiaryName":"ADEOLA STORES","grossAmount":50000.00,"feeAmount":1500.00,"netAmount":48500.00}amount equals netAmount. Match on virtualAccountNumber or externalRef. A credit to an ordinary account (not through a virtual account) has the same shape without the virtualAccount*, grossAmount and netAmount keys.
{"type":"transaction.completed","eventId":"5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a","reference":"TXN20260901101200077","clientReference":"BP-20260901-001","txType":"BILL_PAYMENT","amount":"15000.00","currency":"NGN","sourceAccountNumber":"6020012345","destinationAccountNumber":null,"customerType":"AGENT","sourceChannel":"API","accountNumber":"6020012345","accountName":"ADEOLA STORES","customerName":"ADEOLA STORES","customerPhone":"08012345678","billPaymentRef":"BPY20260901101200077","billerName":"Ikeja Electric","billerCode":"IKEDC","billType":"UTILITY","productName":"Prepaid Electricity","subscriberId":"04512345678","subscriberName":"ADEBAYO OGUNDIMU","feeAmount":"100.00","totalDebit":"15100.00"}The provider’s receipt (token, units, receipt, providerReference) is on the separate bill.payment.completed event, which is raised when the provider confirms. Subscribe to both.
{"type":"transaction.completed","eventId":"7e6d5c4b-3a2f-4e1d-9c8b-7a6f5e4d3c2b","reference":"TXN20260901110000012","clientReference":"PAYOUT-77","txType":"TRANSFER_OUT","amount":25000.00,"currency":"NGN","sourceAccountNumber":"6020012345","destinationAccountNumber":"6020054321","customerType":"AGENT","sourceChannel":"API","accountNumber":"6020012345","accountName":"ADEOLA STORES","customerName":"ADEOLA STORES","customerPhone":"08012345678","beneficiaryName":"BOLA ADEYEMI","beneficiaryAccount":"6020054321","senderName":"ADEOLA STORES","senderAccount":"6020012345","initiatedByPartner":"pa_ab12cd34"}The credit leg arrives separately with txType TRANSFER_IN and the destination account as accountNumber. initiatedByPartner appears when you debited an agent’s account with your own client.
{"type":"bill.payment.completed","eventId":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","reference":"BPY20260901101200077","billerCode":"IKEDC","billerName":"Ikeja Electric","billType":"UTILITY","amount":15000.00,"totalFee":100.00,"subscriberId":"04512345678","subscriberName":"ADEBAYO OGUNDIMU","subscriberAddress":"12 Marina Road, Lagos","token":"1234-5678-9012-3456-7890","units":"312.5","receipt":"IKEDC-2026-000123","providerReference":"CP-99887766","posTransactionId":null,"maskedPan":null,"terminalId":null}Metadata keys by kind of movement
Section titled “Metadata keys by kind of movement”Only the keys that apply are present. Empty strings are used where a value was unknown at posting time; nulls are not written.
txType |
Keys |
|---|---|
NIP_OUTWARD |
beneficiaryName, beneficiaryAccount, beneficiaryBank (6-digit NIBSS code), beneficiaryBankCbnCode (3-digit), beneficiaryBankName, nipSessionId, senderName, senderAccount, feeAmount, vatAmount, stampDutyAmount, clientReference |
NIP_INWARD |
senderName, senderAccount, senderBank, senderBankName, nipSessionId, narration; through a virtual account add virtualAccountNumber, virtualAccountName, externalRef, beneficiaryAccount, beneficiaryName, grossAmount, feeAmount, netAmount (a remote-validated band adds beneficiarySource instead of virtualAccountName and externalRef) |
TRANSFER_OUT, TRANSFER_IN |
beneficiaryName, beneficiaryAccount, senderName, senderAccount, channel; initiatedByPartner, initiatedByPartnerCustomerId when a partner debited a sub-agent |
VA_CREDIT_OUT, VA_CREDIT_IN |
virtualAccountNumber, virtualAccountName, virtualAccountExternalRef, virtualAccountPaymentRef, vaMode, beneficiaryAccount, beneficiaryName, senderName, senderAccount, grossAmount, netAmount, feeAmount, channel, clientReference |
BILL_PAYMENT, AIRTIME_PURCHASE |
billPaymentRef, billerName, billerCode, billType, productName, subscriberId, subscriberName, subscriberAddress, amount, feeAmount, totalDebit, clientReference |
CASH_IN, CASH_OUT |
agentAccountNumber, agentName, customerAccountNumber, customerName, operationType |
| POS legs | terminalId, merchantName, cardMasked, rrn, authCode, posTransactionType |
| Card legs | cardMasked, merchantName, merchantId, terminalId, authCode, rrn, responseCode |
REVERSAL |
reversalOf, reversalReason, originalSessionId, originalAmount, feeAmount, netReversed |
Treat the key set as open: new money-movement features add keys, and your parser should ignore what it does not know.
In the portal
Section titled “In the portal”Webhook endpoints lists every subscription on your client with its event types and filters; opening one shows Deliveries, and a delivery shows the message that was sent with some keys withheld (the count of withheld keys is stated).