Skip to main content

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

TypeDescription
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

  1. Your app collects the user's BVN.
  2. Your server calls POST /v1/kyc/bvn with the BVN and your user reference.
  3. PayKore returns 202 Accepted with a kyc_id and status: "processing".
  4. PayKore calls the verification provider (Mono or Smile Identity) asynchronously.
  5. The provider responds, typically within 3–10 seconds.
  6. PayKore fires a kyc.verified or kyc.failed webhook to your server.
  7. 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:

CodeMeaningRetry-able?
BVN_NOT_FOUNDThe BVN doesn't exist in the banking system.No — ask user to re-enter
BVN_DOB_MISMATCHIf you pass a date of birth for matching, it didn't match the BVN record.No — verify the DOB with the user
PROVIDER_TIMEOUTThe 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).

note

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 valueResult
Any other 11-digit numberverified
00000000000failed with reason BVN_NOT_FOUND
11111111111failed with reason PROVIDER_TIMEOUT

Use these three values to exercise every branch of your webhook handler without depending on real identity data.


Next steps