User Onboarding Workflow
Complete guide to onboarding a new user: create the account, activate it, issue a token, create a wallet, and generate a deposit address.
Onboarding turns a brand-new person into an account that can hold crypto, trade, and withdraw. This guide walks the full sequence end to end, in the order the API expects it.
Overview
Each step builds on the one before it. You create the user with your owner key, activate them, issue a token so that user can act on their own behalf, give the user a wallet to hold funds, then generate an address so money can flow in.
- Create a user account
- Activate the user
- Issue a user token
- Create a wallet
- Generate a deposit address
When you finish, you'll hold a user id, a user token pair, a wallet id, and a deposit address—everything later flows like trading and withdrawals depend on.
Estimated time: 5-10 minutes
Prerequisites: your owner API key (x-api-key)
Every response uses the standard envelope. Send an x-idempotency-key on each POST so a retry never creates a second user, wallet, or address.
Authentication by step
Each step authenticates as the actor that performs it:
- Create, activate, and issue tokens—your owner key,
x-api-key: YOUR_OWNER_API_KEY(SX Connect). - Act on the user (wallets, addresses)—the user token from Step 3, sent as
Authorization: Bearer YOUR_USER_TOKEN.
Step-by-Step Guide
Step 1: Create User Account
Register the person under your customer account. Every user starts as PENDING. Email, phone number, and external_id must each be unique within your customer account.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_OWNER_API_KEY" \
-H "x-idempotency-key: 9a7c3e1f-5b2d-4c8e-a6f0-1d3b5e7f9a2c" \
-d '{
"external_id": "crm-000123",
"account_type": "INDIVIDUAL",
"account_role": "THIRD",
"account_purpose": "INVESTING",
"first_name": "John",
"last_name": "Doe",
"email": "[email protected]",
"phone_country_code": "US",
"phone_number": "2252542523",
"individual": {
"birth_date": "1990-10-15",
"residential_country_code": "US",
"residential_address": "123 Sovera Lane",
"residential_city": "Cheyenne",
"residential_state": "WY",
"residential_postal_code": "82001",
"id_type": "SSN",
"id_number": "123456789",
"id_country_code": "US"
}
}'Response (201 Created):
{
"success": true,
"data": {
"id": "f4057807-52cf-4083-9ecb-283ef354fb2b",
"user_id": "f4057807-52cf-4083-9ecb-283ef354fb2b",
"object": "user",
"external_id": "crm-000123",
"customer_id": "corp-redundant-firewall",
"account_type": "INDIVIDUAL",
"account_role": "THIRD",
"account_purpose": "INVESTING",
"first_name": "John",
"last_name": "Doe",
"email": "[email protected]",
"phone_country_code": "US",
"phone_number": "2252542523",
"status": "PENDING",
"is_active": true,
"individual": {
"birth_date": "1990-10-15",
"residential_country_code": "US",
"residential_address": "123 Sovera Lane",
"residential_city": "Cheyenne",
"residential_state": "WY",
"residential_postal_code": "82001",
"id_type": "SSN",
"id_number_masked": "*****6789",
"id_country_code": "US"
},
"created_at": "2026-10-12T14:30:00Z",
"updated_at": "2026-10-12T14:30:00Z",
"created_by": "65f1c2a9e4b0a1d2c3f4a5b6",
"updated_by": "65f1c2a9e4b0a1d2c3f4a5b6"
},
"meta": {
"timestamp": "2026-10-12T14:30:00Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}Save the user id. Every later step uses it.
See Create User for full request details.
Step 2: Activate the User
Move the user to VERIFIED once they're ready to transact. Tokens are only issued to verified, active users.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/f4057807-52cf-4083-9ecb-283ef354fb2b/activate" \
-H "x-api-key: YOUR_OWNER_API_KEY"The response (200 OK) is the user object with "status": "VERIFIED" and "is_active": true. See Activate User.
Step 3: Issue User Token
Issue the token pair the user presents on every SX Users call. The access token lasts 24 hours; the refresh token lasts 30 days and renews the pair once, without your owner key.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/f4057807-52cf-4083-9ecb-283ef354fb2b/tokens" \
-H "x-api-key: YOUR_OWNER_API_KEY"Response (201 Created):
{
"success": true,
"data": {
"id": "bf1f8aa6-8b55-5650-8ee7-c7a9d29d0b5e",
"object": "user_token",
"user_id": "f4057807-52cf-4083-9ecb-283ef354fb2b",
"token": "utk_test_7Qm4Lr8Tz1V2x9Kf3Bn6Wd1Yb6Jk2Hc5Np8Rf2Lm6Tq",
"token_last4": "9Vx3",
"expires_at": "2026-10-13T14:30:00Z",
"created_at": "2026-10-12T14:30:00Z",
"created_by": "65f1c2a9e4b0a1d2c3f4a5b6",
"refresh_token": "rt_9f2c4e7a1b3d5f80c6e2a4b8d1f3e5a79f2c4e7a1b3d5f80c6e2a4b8d1f3e5a7",
"refresh_expires_at": "2026-11-11T14:30:00Z"
},
"meta": {
"timestamp": "2026-10-12T14:30:01Z",
"version": "v1",
"trace_id": "7d2a9c4e1f3b5a6c8e0d2f4a6b8c0e1d"
}
}Store both token and refresh_token securely—they're shown only once. Renew with the refresh token before expires_at; issuing again retires the previous pair.
See User Tokens for refresh and bulk issuance.
Step 4: Create Wallet
A wallet is where the user's crypto lives. Create one so the account has somewhere to receive deposits, settle trades, and fund withdrawals. Act as the user with the Bearer token from Step 3. Each {network, currency} pair provisions a deposit address, returned in addresses[]. Networks are canonical codes such as BITCOIN or ETHEREUM.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/f4057807-52cf-4083-9ecb-283ef354fb2b/wallets" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_USER_TOKEN" \
-H "x-idempotency-key: 2c4e6a8b-1d3f-4a5c-9e7b-0f2d4c6e8a1b" \
-d '{
"name": "Main Wallet",
"description": "Main wallet for all transactions",
"wallet_type": "VIRTUAL",
"networks": [
{ "network": "BITCOIN", "currency": "BTC" }
]
}'Response (201 Created):
{
"success": true,
"data": {
"id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"wallet_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"object": "wallet",
"user_id": "f4057807-52cf-4083-9ecb-283ef354fb2b",
"customer_id": "corp-redundant-firewall",
"name": "Main Wallet",
"description": "Main wallet for all transactions",
"wallet_type": "VIRTUAL",
"status": "ACTIVE",
"networks": [
{ "network": "BITCOIN", "currency": "BTC" }
],
"addresses": [
{
"id": "ad1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
"address_id": "ad1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
"object": "address",
"wallet_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"currency": "BTC",
"network": "BITCOIN",
"address": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx",
"memo": null,
"created_at": "2026-10-12T14:31:05Z",
"created_by": "66a0e1b2c3d4e5f6a7b8c9d0"
}
],
"created_at": "2026-10-12T14:31:05Z",
"updated_at": "2026-10-12T14:31:05Z",
"created_by": "66a0e1b2c3d4e5f6a7b8c9d0",
"updated_by": "66a0e1b2c3d4e5f6a7b8c9d0"
},
"meta": {
"timestamp": "2026-10-12T14:31:05Z",
"version": "v1",
"trace_id": "1e3c5a7b9d0f2e4a6c8b0d2f4e6a8c0b"
}
}Save the wallet id. Creating the wallet already provisions one address per {network, currency} pair, and you can generate more next.
See Create Wallet for full request details.
Step 5: Generate Deposit Address
Generate a deposit address so the user can fund the wallet—anyone can send the chosen currency on that network to this address, and it lands in the wallet. If the wallet already has an address for that currency and network, you get the existing one back with 200 OK instead of 201 Created. Act as the user with the Bearer token.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/wallets/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d/addresses" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_USER_TOKEN" \
-d '{
"currency": "ETH",
"network": "ETHEREUM"
}'Response (201 Created):
{
"success": true,
"data": {
"id": "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"address_id": "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"object": "address",
"wallet_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"currency": "ETH",
"network": "ETHEREUM",
"address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"memo": null,
"created_at": "2026-10-12T14:32:40Z",
"created_by": "66a0e1b2c3d4e5f6a7b8c9d0"
},
"meta": {
"timestamp": "2026-10-12T14:32:40Z",
"version": "v1",
"trace_id": "5f7b9d1e3a5c7e9b1d3f5a7c9e1b3d5f"
}
}See Wallet Addresses for full request details.
With an address in hand, onboarding is done: the user can receive funds and you can move on to trading or withdrawals.
Next Steps
After onboarding:
- Check Balance—Monitor the wallet balance
- Execute Trade—Start trading
- Process Withdrawal—Withdraw funds
Troubleshooting
User Already Exists
Error: 409 EMAIL_ALREADY_IN_USE, PHONE_ALREADY_IN_USE, EXTERNAL_ID_ALREADY_IN_USE, or USER_ID_ALREADY_IN_USE
Solution: source.pointer names the field that's already taken. Use a unique value, or find the existing user with GET /v1/users?filter[where][email][eq]=... or filter[where][external_id][eq]=....
Validation Failed
Error: 422 VALIDATION_ERROR
Solution: Check each entry in errors[]; source.pointer names the field. The phone_number must be valid for the given phone_country_code, and the holder must be 18 or older.
Token Not Issued
Error: 422 INVALID_STATE_TRANSITION
Solution: The user isn't VERIFIED and active. Call Activate User, then issue the token again.
Token Expired
Error: 401 UNAUTHORIZED on an SX Users call
Solution: The access token is older than 24 hours. Renew it with the refresh token (grant_type: refresh_token), or issue a new pair with your owner key if the refresh token expired too.
Wallet Creation Failed
Error: 400 WALLET_NAME_ALREADY_EXISTS or 422 VALIDATION_ERROR
Solution: The name must be unique per user, and each networks[].network must be a supported network code.
Support
Need help? Email [email protected]