Prerequisites
- Your API credentials and a working signing helper; see Authentication.
- A reachable webhook endpoint that verifies signatures.
- Enough in your company balance to cover the card’s opening amount plus fees. The $2.00 card in this tutorial takes $4.00: the $2.00 load, a $1.00 top-up fee, and a $1.00 creation fee. See What creating a card costs.
2000000 is $2.00.
1
Verify the cardholder
Every card starts with a verified cardholder. Submit their identity details to Card KYC. The response returns the With a BVN, approval can come back in the same response:If a customer with that
customer_id you pass to Create card. Identity details are captured here, so you don’t send them again when you create the card. Card KYC covers document rules, statuses, and rejections.KYC can be asynchronous. Wait until the customer is approved before you issue a card. If
status comes back pending, the outcome arrives on the virtualcard.user.kyc.complete or virtualcard.user.kyc.failed webhook. If the cardholder has a step to finish, the response also carries verification_link, the page they must complete, with verification_link_expires_at.id_number or phone number already exists under a different email, Card KYC returns 400 with that customer’s ID in the detail. Resubmit with customer_id in place of the customer object to verify the existing customer.Store the customer_id. It’s the input to the next step.2
Create the card
Issue the card against the approved The opening
customer_id. name is optional and defaults to the customer’s name. Full schema: Create card.webhook_url and contactless_payment are optional; see Create card for every field.The response returns the card in status pending, with no card number yet:amount and both fees are taken from your company balance at this point. If it can’t cover them, the call fails with 400 and extensions.error_code COMPANY_INSUFFICIENT_BALANCE.Store the card id. Every later call uses it.3
Wait for the activation webhook
The issuer confirms creation with This is the signal to show the card in your UI. Fetch the PAN, CVV, and expiry with Get secure card details, display them once, and do not store the CVV. If you’ve registered a card encryption key, they arrive encrypted; see Decrypt card details. If creation fails you get
virtualcard.created.completed:virtualcard.created.failed with a failureReason.You can also check with Get card. Once the card is active, it shows the last four digits and the opening balance:4
Fund the card
Load USD from your balance with Fund or withdraw balance.
type: "fund" credits the card; type: "withdraw" moves value back.reference is your idempotency key: retrying with the same reference returns the original transaction instead of double-funding. Confirmation arrives as virtualcard.topup.completed; a balance or card status problem is refused straight away with a 400. If a top-up fails later during processing, you get virtualcard.topup.failed with a reason.5
Handle a spend
When the cardholder pays a merchant, the settled charge reaches you as Branch on
virtualcard.transaction.settlement:event first, then on transaction_type; several events share a name across pending and settled states. The full catalog is on Webhook events. The same activity is queryable with List card transactions.6
Handle a refund or reversal
Merchants return money two ways, and both arrive as webhooks:
virtualcard.transaction.refund after settlement, virtualcard.transaction.reversed when an authorization is canceled before settlement. Credit your user’s view of the balance from these events. Each event has its own reference, so match it to the original spend by card and merchant.7
Freeze, unfreeze, or terminate
Give users a freeze button, and keep termination for endings:Freezing blocks new authorizations immediately and is reversible via Freeze or unfreeze. Terminate is permanent, sweeps the remaining balance back to your wallet, and cannot be called within 24 hours of creation. Details and the automatic transitions are in Freeze, unfreeze, or terminate a card.
Production checklist
- Idempotency keys on card creation and every top-up.
- Webhook handler verifies the signature, de-duplicates on
event_id, and returns200fast. - Card status tracked in your database and reflected in your UI.
- CVV never stored;
masked_panshown only after user authentication. - Decline handling wired: repeated declines carry fees and can terminate the card. See Fees and decline rules.
Next
Fund a card
Load the card you just issued.
Receive card webhooks
Learn what the card is doing.