Wirex Hosted KYC
Collect basic user data and let users complete KYC verification through Wirex's hosted flow.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Getting Started | Platform overview and setup |
| Api Basics | Required headers and request configuration |
| Authentication | How to obtain access tokens |
| Webhooks | Receive status notifications |
| Onboarding | User and wallet registration |
| User Profile | User capabilities and status |
When to Use
- You want Wirex to handle KYC verification
- You collect minimal user data at registration
- Users will complete identity verification in the Wirex interface
Registration Methods
Two registration endpoints are available depending on your authentication flow:
| Method | Endpoint | Auth Flow |
|---|---|---|
| S2S Registration | POST /api/v2/user | S2S token (backend-to-backend) |
| Privy Registration | POST /api/v1/user/retail | Privy-based auth (frontend) |
Option A: S2S Registration
For backend-to-backend integration using S2S tokens.
POST /api/v2/user
{
"user_address": "0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE",
"initial_data": {
"profile": {
"email": "[email protected]"
},
"residence_address": {
"country": "GB"
}
}
}Important: The user_address must be the user's EOA address (the signer), not the Smart Wallet address.
user_addressreplaces the formerwallet_address. Both are accepted; if both are supplied they must match. New integrations should senduser_address.
| Field | Type | Required | Validation |
|---|---|---|---|
user_address | string | Yes | User's EOA address |
initial_data.profile.email | string | Yes | Valid email format, must be unique |
initial_data.residence_address.country | string | Yes | ISO 3166-1 alpha-2 code |
Option B: Privy Registration
For partners using Privy authorization flow. The user context is derived from the Privy token.
POST /api/v1/user/retail
{
"email": "[email protected]",
"country": "GB"
}| Field | Type | Required | Validation |
|---|---|---|---|
email | string | Yes | Valid email format, must be unique |
country | string | Yes | ISO 3166-1 alpha-2 code |
Code Examples (S2S Registration)
async function registerUserHostedKYC(accessToken, userAddress, email, country, chainId, baseUrl) {
const response = await fetch(`${baseUrl}/api/v2/user`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'X-Chain-Id': chainId.toString(),
},
body: JSON.stringify({
user_address: userAddress,
initial_data: {
profile: { email },
residence_address: { country },
},
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Registration failed: ${error.error_description}`);
}
return response.json();
}
// Usage
const { id } = await registerUserHostedKYC(
access_token,
'0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE',
'[email protected]',
'GB',
8453,
'https://api-baas.wirexapp.com'
);import requests
def register_user_hosted_kyc(access_token, user_address, email, country, chain_id, base_url):
response = requests.post(
f"{base_url}/api/v2/user",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
"X-Chain-Id": str(chain_id),
},
json={
"user_address": user_address,
"initial_data": {
"profile": {"email": email},
"residence_address": {"country": country},
},
},
)
response.raise_for_status()
return response.json()
# Usage
user = register_user_hosted_kyc(
access_token,
"0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE",
"[email protected]",
"GB",
8453,
"https://api-baas.wirexapp.com"
)func RegisterUserHostedKYC(accessToken, userAddress, email, country string, chainID int, baseURL string) (string, error) {
reqBody, _ := json.Marshal(map[string]interface{}{
"user_address": userAddress,
"initial_data": map[string]interface{}{
"profile": map[string]string{"email": email},
"residence_address": map[string]string{"country": country},
},
})
req, _ := http.NewRequest("POST", baseURL+"/api/v2/user", bytes.NewBuffer(reqBody))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Chain-Id", fmt.Sprintf("%d", chainID))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var result struct{ ID string `json:"id"` }
json.NewDecoder(resp.Body).Decode(&result)
return result.ID, nil
}Flow
sequenceDiagram
participant App as Your App
participant BaaS as Wirex API
participant SumSub
App->>BaaS: 1. POST /api/v2/user or /api/v1/user/retail
BaaS-->>App: { id }
App->>BaaS: 2. GET /verification-link or /verification-token
BaaS-->>App: { url } or { token }
App->>SumSub: 3. User completes KYC (redirect or SDK)
SumSub->>BaaS: 4. Verification result
Note over BaaS: 5. Account activated
BaaS->>App: POST /webhook/users (verification_status)
KYC Completion Options
After registration, the user must complete KYC verification. Two options are available:
| Option | Endpoint | Use Case |
|---|---|---|
| Verification Link | GET /api/v1/user/verification-link | Redirect user to hosted SumSub widget |
| SDK Token | GET /api/v1/user/verification-token | Embed SumSub SDK in your app |
Option A: Verification Link
Redirect the user to a hosted SumSub widget where they complete all KYC steps.
GET /api/v1/user/verification-link
Response:
{
"url": "https://verify.sumsub.com/..."
}Open this URL in a browser or webview. The user completes verification in the hosted interface.
Option B: SDK Token
Obtain a token to initialize the SumSub SDK within your application.
GET /api/v1/user/verification-token
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Use this token to initialize the SumSub WebSDK or Mobile SDK in your frontend. For integration details, see the SumSub SDK documentation.
What Happens Next
- User completes identity verification (document upload, selfie, etc.)
- SumSub sends verification result to Wirex
- Wirex sends a webhook to your endpoint with updated
verification_status - User account is activated upon successful verification
The user cannot perform restricted operations (card issuance, fiat withdrawals) until KYC is approved.
For webhook payload details, see User Onboarding - Webhooks.
Phone Number: The phone number the user enters in the SumSub form is stored on the profile and automatically marked as confirmed. It will be used for 3DS Authentication OTP codes and Read Card Details OTP verification. No separate confirmation step is required.
Error Handling
Validation Errors
| Field | Error | Pattern/Requirement |
|---|---|---|
user_address | Invalid format | ^0x[a-fA-F0-9]{40}$ (EVM) |
email | Invalid format | Valid email address |
email | Missing | Required field |
country | Invalid format | ISO 3166-1 alpha-2 (^[A-Z]{2}$) |
country | Missing | Required field |
Registration Errors
| Error Reason | Description | Resolution |
|---|---|---|
ErrorNotFound | Wallet not registered in Accounts contract | Complete on-chain registration first |
ErrorAlreadyExists | Email already exists | Use a different email address |
ErrorAlreadyExists | Wallet address already exists | User already registered |
Error Response Format
{
"error_reason": "ErrorInvalidField",
"error_description": "Invalid format for email",
"error_category": {
"category": "CategoryValidationFailure",
"http_status_code": 400
},
"error_details": [
{ "key": "field", "details": "email" },
{ "key": "issue", "details": "invalid_format" }
]
}Updated 1 day ago

