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 KYC | External Attestation | |
|---|---|---|
| Mode value | urbanpayx_kyc | external_attestation |
| Who verifies | UrbanPayX (via hosted provider) | You (your own process) |
| User experience | User completes ID + liveness check on hosted page | You verify identity through your own process, then tell UrbanPayX |
| Status field | kyc_status | external_verification_status |
| Payment-eligible status | approved | verified |
| Best for | When you need compliant identity verification without building it yourself | When 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:
| Level | What it checks |
|---|---|
id-and-liveness | ID document upload + live selfie check |
id-only | ID document upload only |
idv-and-phone-verification | ID 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_IDStatus transitions
none --> pending --> approved
--> rejected
| Status | Meaning | Can generate payment link? |
|---|---|---|
none | No verification initiated | No |
pending | Verification in progress | No |
approved | Identity verified | Yes |
rejected | Verification failed | No |
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
| Status | Meaning | Can generate payment link? |
|---|---|---|
pending | Waiting for your attestation | No |
verified | You confirmed identity | Yes |
rejected | You rejected identity | No |
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_TOKENVerification 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
| Method | Path | Description |
|---|---|---|
| POST | /kyc/users | Create a single user |
| POST | /kyc/users/bulk | Bulk create users (max 500) |
| PATCH | /kyc/users/USER_ID/verification-mode | Switch verification mode |
| POST | /kyc/verify | Initiate UrbanPayX KYC verification |
| GET | /kyc/status/USER_ID | Get user verification status |
| GET | /kyc/lists | List all users (paginated) |
| POST | /kyc/external/status/USER_ID | Update external verification status |
| POST | /kyc/external/status/bulk | Bulk update external status (max 500) |
| PATCH | /kyc/users/USER_ID | Update a user's profile |
| POST | /kyc/users/USER_ID/activate | Reactivate a user |
| POST | /kyc/users/USER_ID/deactivate | Deactivate a user |
| GET | /kyc/levels | List verification levels available |
| GET | /kyc/summary | Count users by verification status |
| GET | /kyc/decision/USER_ID | Full decision, reconciled on demand |
| GET | /kyc/detail/USER_ID | Latest curated verification record |
| GET | /kyc/detail/USER_ID/history | All verification records for the user |
| GET | /kyc/detail/USER_ID/documents/{role} | Download a captured document |
| POST | /kyc/USER_ID/resubmit | Ask the user to redo failed checks |
| POST | /kyc/USER_ID/manual-review | Approve or reject a user by hand |
Related guides
- KYB Verification Guide — The same flows for business customers
- Core Concepts — How Users fit into the domain model
- Getting Started — End-to-end flow including user verification
- Error Reference — All KYC-related errors explained
- Roles and Permissions — Which roles can manage KYC