Card KYC
What Is Card KYC?
Before a customer can be given a virtual card, they must verify their identity — this is "KYC" (Know Your Customer). The customer submits their details (name, date of birth, an ID, address), our partner Miden checks them, and once the customer is approved, a card can be created.
No approval = no card. This is a compliance requirement, not a bug.
This guide covers submitting KYC, checking status, and receiving the asynchronous outcome by webhook.
How It Works
Submit the customer's identity details. Bitnob creates or uses a customer record and forwards the submission to the verification provider. You get back a customer_id and an initial status
Verification runs asynchronously. Simple submissions may return approved almost immediately; others sit at pending while documents and checks are processed
Learn the outcome either by polling the status, or by receiving a webhook, which is recommended. The webhook is the terminal signal
Once approved, proceed to card issuance
BVN and NIN are verified instantly. All other ID types return a result within 10 minutes.
The Customer Journey
The same flow as the customer experiences it.
The customer submits their identity information, usually through the partner app
Verification happens automatically. Some submissions come back approved in seconds, others sit under review for a while
The final result is approved or rejected
Only after approved can the card be created
Prerequisites
item | value |
|---|---|
Environment | Sandbox for testing, production for live customers |
Auth and endpoint | Use your assigned Bitnob endpoint and API credentials. Confirm the exact host and auth headers with your Bitnob contact |
Field naming | Field names are snake_case. Tooling also accepts the camelCase equivalents — customer_id and customerId |
Submitting KYC
field | type | required | notes |
|---|---|---|---|
customer | object | Required | The identity to verify. A successful submission creates the customer and returns their customer_id |
occupation | string | Required | The customer's occupation, in lowercase snake_case |
employment_status | string | Required | For example employed |
account_purpose | string | Required | What the customer intends to use the card for |
annual_salary | string | Required | Declared annual income in whole major units, sent as a string |
expected_monthly_volume | string | Required | Anticipated monthly spend in whole major units, sent as a string |
terms_of_service_accepted | bool | Required | Must be true |
place_of_birth | string | Optional | The customer's place of birth |
webhook_url | string | Optional | Per-submission callback URL for the outcome. Empty fans out to your company's configured webhook |
id_front_image | string | Conditional | Base64-encoded image of the ID front. Required for document id types — never for bvn or nin |
redirect_url | string | Optional | A redirect link |
The customer Object
field | required | notes |
|---|---|---|
customer_type | Required | Lowercase individual or business. Capitalised values are rejected |
first_name | Required | Required for businesses too — the natural person behind the account |
last_name | Required | Required for businesses too |
date_of_birth | Required | YYYY-MM-DD. Required for businesses too |
id_type | Required | See Identity Document Rules |
id_number | Required | bvn, nin, or document number. Must be unique. Stored encrypted at rest |
email | Required | Per-company reconciliation key |
phone_number | Optional | Without dial code. Send it with dial_code |
dial_code | Optional | For example +234. Send it with phone_number |
business_name | Required for businesses | Sent alongside the personal fields, not instead of them |
registration_number | Required for businesses | Business or RC number |
line1, city, state, postal_code, country | Required | Address. For bvn and nin, country must be Nigeria — NGA |
line2 | Optional | Address line 2 |
first_name and last_name are verified against the identity document. Submit them exactly as they appear on the document.
Response
field | notes |
|---|---|
customer_id | The Bitnob customer id, persisted. Use this for status lookups and card issuance |
normalized_status | Read this field. Provider-independent status — initiated, pending, approved, or rejected |
status | The provider's own vocabulary |
email, first_name, last_name | Echoed back for reconciliation |
completion_link | A redirect link |
Checking Status
Poll the status if you are not using webhooks, or to reconcile.
field | notes |
|---|---|
normalized_status | Read this. none, initiated, pending, approved, or rejected. none means no KYC on record |
status | Provider vocabulary |
reason | Rejection labels when pending or rejected |
completion_link | A redirect link |
Status Vocabulary
Always integrate against normalized_status — it is the same regardless of provider.
normalized_status | meaning | terminal |
|---|---|---|
none | No KYC on record for this customer | No |
initiated | Submission received and created | No |
pending | Under review — documents or checks in progress | No |
approved | Verified. The customer may be issued a card | Yes |
rejected | Verification failed. See reason | Yes |
Provider Mapping
How the raw status collapses into normalized_status.
raw provider status | normalized |
|---|---|
action_required | pending |
denied | rejected |
Any other value | Passed through unchanged |
Identity Document Rules
Accepted id_type values are passport, nin, bvn, national_id, vnin, drivers_license, voters_card, and ghana_card.
rule | requirement |
|---|---|
bvn and nin | country must be Nigeria — NGA. Never send id_front_image |
nin | date_of_birth is required, in YYYY-MM-DD format |
Document types — passport, drivers_license, ghana_card, national_id, voters_card, vnin | Send id_front_image as a base64-encoded string |
Country codes | ISO 3166-1 alpha-3 — for example NGA, GHA, USA |
The photo must show the front of the document in full, in focus and in date. Blurred, partial, or expired images are the most common cause of rejection.
Webhooks
KYC is asynchronous. Rather than polling, receive the terminal outcome as a webhook. Configure a webhook_url per submission, or a company-wide webhook with Bitnob.
Events
virtualcard.user.kyc.pending — Verification is under review (not final).
virtualcard.user.kyc.complete — Approved (kycPassed: true).
virtualcard.user.kyc.failed — Rejected (kycPassed: false, see reason).
data field | notes |
|---|---|
customerId | The Bitnob customer id. Your reconciliation key alongside customerEmail |
customerEmail | Per-company reconciliation key |
kycPassed | true on complete, false on failed |
reason | Populated on failure, with rejection labels |
Handling
Respond 2xx quickly. Treat delivery as at-least-once and make your handler idempotent, deduplicating on customerId and event
Do not treat pending as terminal. Only complete and failed are final
Verify signatures per your Bitnob webhook configuration
Common Errors
message | cause or fix |
|---|---|
invalid created_by: must be a valid UUID | created_by must be a UUID, not an email |
customer_type must be Individual or Business | Fix customer.customer_type |
KYC is required before a card can be issued for this customer | Card issuance was attempted before KYC reached approved |
card KYC is not available | The KYC service is unavailable for the requested environment. Ensure you are calling the correct one, and contact Bitnob if it persists |
Rate limits apply to KYC calls. Retry with backoff on transient errors.
Support Notes
What To Tell The Customer
Plain-language phrasing for each status.
status | what the customer should hear |
|---|---|
none | We have not received your verification yet — please submit your details to continue |
initiated | We have your details and verification has started |
pending | Your verification is under review. This can take a little time, and there is nothing you need to do right now — we will update you once it is complete |
approved | You are verified. Your card can now be created |
rejected | We could not verify your identity. Reason: the reason returned on the record. Please correct it and submit again |
Only approved and rejected are final. pending means wait — it is not an error.
"Card creation says KYC is required"
The customer is not approved yet. Check the status and branch on it.
pending — ask the customer to wait, it is still under review. Wait for the virtualcard.user.kyc.complete webhook
rejected — the customer must fix the reason and resubmit
none — no KYC has been submitted for this customer at all. Ask them to complete it
"KYC came back pending and never moved"
Verification is still processing, or a document was unreadable. Check for a virtualcard.user.kyc.failed webhook with a reason, and re-check the status.
"The verification was rejected"
The specific cause is in reason on the record and on the virtualcard.user.kyc.failed webhook — commonly a blurred document, mismatched details, or an expired ID. Give the customer the specific reason and how to fix it, then have them resubmit.
"Which id becomes the card owner?"
The customer_id returned on submission — always the Bitnob id, never the provider's internal id.
"nin or bvn submission rejected on country"
bvn and nin require country to be NGA, and no ID image. The photo is only needed for documents like a passport or driver's licence.
"The ID photo keeps failing"
The image is blurred, cropped or expired. Ask for a clear, in-date photo showing the front of the document in full — see Identity Document Rules.
"KYC is approved but the card still will not create"
Confirm normalized_status really is approved rather than pending. If it is approved and issuance still fails, this is not a KYC problem — escalate.
Expected Behaviour, Not A Bug
pending is normal. Verification is asynchronous, and some checks take longer than others
"KYC is required before a card can be issued for this customer" ahead of approval is the compliance rule working, not an error
Some customers are approved in seconds while others sit under review. Different id types and documents take different amounts of time
Escalation
Escalate to engineering when:
normalized_status is approved but card issuance still fails
A submission has been pending far beyond the expected window, with no webhook and no change on re-check
The customer hits an error that is not covered above — for example card KYC is not available
What To Collect First
item | detail |
|---|---|
Company | company_id, and the partner name |
Customer | The customer_id returned on submission, and the customer email |
Status | The current normalized_status, the raw status, and any reason |
Document | The id_type used, and whether an id_front_image was sent |
Timing | Approximate time of the submission, or of the failed card creation |
Error | The exact error message and HTTP status, if there was one |
Hand this to the engineering on-call together with a link to this guide.