Flows

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.

  1. Create a user account
  2. Activate the user
  3. Issue a user token
  4. Create a wallet
  5. 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.

POSThttps://api.sandbox.sovera.io/sovx/v1/users
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.

POSThttps://api.sandbox.sovera.io/sovx/v1/users/:user_id/activate
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.

POSThttps://api.sandbox.sovera.io/sovx/v1/users/:user_id/tokens
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.

POSThttps://api.sandbox.sovera.io/sovx/v1/users/:user_id/wallets
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.

POSThttps://api.sandbox.sovera.io/sovx/v1/wallets/:wallet_id/addresses
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:

  1. Check Balance—Monitor the wallet balance
  2. Execute Trade—Start trading
  3. 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]

On this page