KYC Verification Guide

Two verification modes, step-by-step flows, status transitions, and bulk operations.

KYC Verification Guide

UrbanPayX verifies the identity of the users you create, either through its own hosted flow or by recording a result you obtained elsewhere. This guide covers both verification modes, step-by-step flows, status transitions, and bulk operations.


Why verify

Open banking and AML rules generally require you to know who is paying before you accept funds from them. UrbanPayX gives you the verification tooling and stores the outcome, but it does not enforce it: no endpoint checks kyc_status. Where verification sits in your flow, and what you do with a rejected result, is your decision.


Two verification modes

UrbanPayX supports two approaches to identity verification. Choose based on whether you want UrbanPayX to handle verification or do it yourself.

UrbanPayX KYCExternal Attestation
Mode valueurbanpayx_kycexternal_attestation
Who verifiesUrbanPayX (via hosted provider)You (your own process)
User experienceUser completes ID + liveness check on hosted pageYou verify identity through your own process, then tell UrbanPayX
Status fieldkyc_statusexternal_verification_status
Payment-eligible statusapprovedverified
Best forWhen you need compliant identity verification without building it yourselfWhen you already have a KYC process and want to bring your own verification results

Your client account has a default verification mode. When you create a user without specifying verification_mode, it inherits the default. You can override the mode per user.


UrbanPayX KYC flow

This mode uses a hosted verification page where users upload identity documents and complete a liveness check.

Step 1 — Create the user

POST /api/v1/kyc/users
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
  "email": "[email protected]",
  "full_name": "Jane Smith",
  "verification_mode": "urbanpayx_kyc"
}

The user is created with kyc_status: none.

Step 2 — Initiate verification

POST /api/v1/kyc/verify
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
  "email": "[email protected]",
  "full_name": "Jane Smith",
  "level_name": "id-and-liveness"
}

Verification levels:

LevelWhat it checks
id-and-livenessID document upload + live selfie check
id-onlyID document upload only
idv-and-phone-verificationID document + phone number verification

The response includes a verification_url — this is a hosted page where the user completes the identity check. The link is valid for 7 days.

{
  "user_id": "uuid",
  "kyc_status": "pending",
  "verification_url": "https://verification-provider.com/...",
  "message": "KYC verification initiated"
}

Step 3 — User completes verification

Send the verification_url to your user. They will upload their ID document and (for liveness checks) take a live selfie. This happens entirely on the hosted verification page — no integration work needed on your side.

Step 4 — Receive the result

When the verification is complete, UrbanPayX sends a kyc.status_changed webhook to your configured webhook URL:

{
  "type": "kyc.status_changed",
  "data": {
    "user_id": "uuid",
    "previous_status": "pending",
    "current_status": "approved"
  }
}

You can also poll the status:

GET /api/v1/kyc/status/USER_ID

Status transitions

none --> pending --> approved
                --> rejected
StatusMeaningCan generate payment link?
noneNo verification initiatedNo
pendingVerification in progressNo
approvedIdentity verifiedYes
rejectedVerification failedNo

If a user is rejected: The rejection is final for that verification attempt. You can check the reason and, if appropriate, initiate a new verification attempt.

If a user is already approved: Calling the verify endpoint returns the current approved status immediately without generating a new link.


External attestation flow

This mode lets you use your own identity verification process and report the result to UrbanPayX.

Step 1 — Create the user

POST /api/v1/kyc/users
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
  "email": "[email protected]",
  "full_name": "Jane Smith",
  "verification_mode": "external_attestation"
}

The user is created with external_verification_status: pending.

Step 2 — Verify the user through your own process

Run your own identity checks (document verification, database lookups, manual review — whatever your process requires).

Step 3 — Report the result

POST /api/v1/kyc/external/status/USER_ID
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
  "status": "verified",
  "provider": "your-kyc-provider",
  "reference": "your-internal-ref-123",
  "reason": "Identity verified via document check",
  "payload": {
    "document_type": "passport",
    "verified_at": "2026-03-26"
  }
}

The provider, reference, reason, and payload fields are optional but recommended for your audit trail. All status changes are recorded in an audit log with the acting account ID and timestamp.

Status transitions

pending --> verified
        --> rejected
StatusMeaningCan generate payment link?
pendingWaiting for your attestationNo
verifiedYou confirmed identityYes
rejectedYou rejected identityNo

Switching verification modes

You can change a user's verification mode after creation:

PATCH /api/v1/kyc/users/USER_ID/verification-mode
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
  "verification_mode": "external_attestation"
}

The user retains their existing verification data for both modes. The system checks eligibility based on the active mode.


Bulk operations

For onboarding large numbers of users, UrbanPayX supports bulk endpoints.

Bulk create users

Create up to 500 users in a single request:

POST /api/v1/kyc/users/bulk
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
  "users": [
    { "email": "[email protected]", "full_name": "User One" },
    { "email": "[email protected]", "full_name": "User Two", "verification_mode": "external_attestation" }
  ]
}

The response reports per-item success or failure, supporting partial success:

{
  "total": 2,
  "succeeded": 2,
  "failed": 0,
  "results": [
    { "index": 0, "email": "[email protected]", "success": true, "user": {} },
    { "index": 1, "email": "[email protected]", "success": true, "user": {} }
  ]
}

Bulk update external verification

Update verification status for up to 500 users at once (external attestation mode only):

POST /api/v1/kyc/external/status/bulk
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
  "items": [
    {
      "user_id": "uuid-1",
      "status": "verified",
      "provider": "your-provider",
      "reference": "ref-001"
    },
    {
      "user_id": "uuid-2",
      "status": "rejected",
      "provider": "your-provider",
      "reference": "ref-002",
      "reason": "Document expired"
    }
  ]
}

This also supports partial success — individual items can fail (e.g., wrong verification mode) without blocking the rest.


Listing users

Retrieve all users with pagination:

GET /api/v1/kyc/lists?page=1&page_size=50
Authorization: Bearer YOUR_ACCESS_TOKEN

Verification and payments

Payment link generation does not check verification state. A user whose kyc_status is none, pending, rejected, or on_hold can be given a payment link exactly like an approved one, and there is no verification-related error code on the payment endpoint.

If you want an approved-only policy, read kyc_status (or external_verification_status for externally attested users) and gate the call in your own code. The kyc.status_changed webhook is the cheapest way to keep that state fresh.


API endpoints reference

MethodPathDescription
POST/kyc/usersCreate a single user
POST/kyc/users/bulkBulk create users (max 500)
PATCH/kyc/users/USER_ID/verification-modeSwitch verification mode
POST/kyc/verifyInitiate UrbanPayX KYC verification
GET/kyc/status/USER_IDGet user verification status
GET/kyc/listsList all users (paginated)
POST/kyc/external/status/USER_IDUpdate external verification status
POST/kyc/external/status/bulkBulk update external status (max 500)
PATCH/kyc/users/USER_IDUpdate a user's profile
POST/kyc/users/USER_ID/activateReactivate a user
POST/kyc/users/USER_ID/deactivateDeactivate a user
GET/kyc/levelsList verification levels available
GET/kyc/summaryCount users by verification status
GET/kyc/decision/USER_IDFull decision, reconciled on demand
GET/kyc/detail/USER_IDLatest curated verification record
GET/kyc/detail/USER_ID/historyAll verification records for the user
GET/kyc/detail/USER_ID/documents/{role}Download a captured document
POST/kyc/USER_ID/resubmitAsk the user to redo failed checks
POST/kyc/USER_ID/manual-reviewApprove or reject a user by hand

Related guides