KYC Verification
KYC (Know Your Customer) verification confirms a user's real-world identity before they can transact above certain thresholds.
Why KYC matters
CBN (Central Bank of Nigeria) regulations require financial service providers to verify the identity of users transacting above defined limits. For wallets classified at Tier 1, BVN verification becomes mandatory once a user exceeds ₦50,000 in a single transaction or ₦300,000 cumulative in a month.
This is not optional for live deployments. Attempting to process transactions above these thresholds for unverified users will be blocked at the API level once your account moves to production.
Verification types
| Type | Description |
|---|---|
| BVN (Bank Verification Number) | An 11-digit number tied to the user's existing bank account(s). The most common verification method — most adult Nigerians with any bank account already have one. |
| NIN (National Identification Number) | A government-issued identity number. Stronger than BVN since it's tied directly to national ID records rather than banking relationships. |
| CAC (Corporate Affairs Commission) | Business registration verification, for merchant/business accounts rather than individual users. On the roadmap — not yet available. |
BVN verification flow
- Your app collects the user's BVN.
- Your server calls
POST /v1/kyc/bvnwith the BVN and your user reference. - PayKore returns
202 Acceptedwith akyc_idandstatus: "processing". - PayKore calls the verification provider (Mono or Smile Identity) asynchronously.
- The provider responds, typically within 3–10 seconds.
- PayKore fires a
kyc.verifiedorkyc.failedwebhook to your server. - You update your user's verification status in your own database.
Initiating verification:
curl -X POST https://api.paykore.dev/v1/kyc/bvn \
-H "X-API-Key: sk_test_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"user_ref": "user_123",
"bvn": "22190000000"
}'
Response:
{
"kyc_id": "kyc_8dEfG9hIjK",
"user_ref": "user_123",
"type": "bvn",
"status": "processing",
"created_at": "2025-06-01T14:00:00Z",
"meta": {
"request_id": "req_4vWxY5zAbC"
}
}
Checking KYC status
curl https://api.paykore.dev/v1/kyc/user_123/status \
-H "X-API-Key: sk_test_YOUR_KEY_HERE"
Response:
{
"user_ref": "user_123",
"type": "bvn",
"status": "verified",
"verified_at": "2025-06-01T14:00:08Z",
"failure_reason": null,
"meta": {
"request_id": "req_7yZaB8cDeF"
}
}
If verification failed, status is "failed" and failure_reason is populated with one of the codes listed below.
Handling KYC webhooks
kyc.verified:
{
"event": "kyc.verified",
"data": {
"kyc_id": "kyc_8dEfG9hIjK",
"user_ref": "user_123",
"type": "bvn",
"status": "verified",
"verified_at": "2025-06-01T14:00:08Z"
},
"timestamp": "2025-06-01T14:00:09Z",
"webhook_id": "wh_9bCdE1fGhI"
}
kyc.failed:
{
"event": "kyc.failed",
"data": {
"kyc_id": "kyc_8dEfG9hIjK",
"user_ref": "user_123",
"type": "bvn",
"status": "failed",
"failure_reason": "BVN_DOB_MISMATCH"
},
"timestamp": "2025-06-01T14:00:09Z",
"webhook_id": "wh_2gHiJ3kLmN"
}
Common failure reasons:
| Code | Meaning | Retry-able? |
|---|---|---|
BVN_NOT_FOUND | The BVN doesn't exist in the banking system. | No — ask user to re-enter |
BVN_DOB_MISMATCH | If you pass a date of birth for matching, it didn't match the BVN record. | No — verify the DOB with the user |
PROVIDER_TIMEOUT | The verification provider didn't respond in time. | Yes — safe to retry the request |
What PayKore does with the data
BVN and NIN values are encrypted at rest using AES-256-GCM. They are never written to application logs. PayKore does not share this data with any third party other than the verification provider used to perform the check (Mono or Smile Identity).
After verification completes, the raw BVN/NIN is not retrievable through the API. Only status, type, and verified_at are exposed on subsequent GET requests — the original identifier is write-only.
Sandbox testing
In sandbox, verification results are deterministic based on the BVN value you submit:
| BVN value | Result |
|---|---|
| Any other 11-digit number | verified |
00000000000 | failed with reason BVN_NOT_FOUND |
11111111111 | failed with reason PROVIDER_TIMEOUT |
Use these three values to exercise every branch of your webhook handler without depending on real identity data.
Next steps
- PII and Compliance → — Broader compliance obligations beyond KYC.
- Webhooks Overview → — Reliable webhook delivery and signature verification.
- Wallets → — How wallet limits change after a user is verified.