SX Connect

Users and Events

Manage your end users' lifecycle—create, list, read, update, activate, deactivate, and delete them—and read the webhook events sent to your account.

Manage the full lifecycle of your end users: create them, read and update their profile, activate or deactivate access, and delete them. A user must exist and be VERIFIED before you issue a token, open wallets, or transact on their behalf. The last section lists the webhook events your account produced.

Authentication: every endpoint on this page requires your Owner API Key. A user token gets 403 FORBIDDEN.

x-api-key: YOUR_OWNER_API_KEY

Every response uses the standard envelope and error format. POST and PATCH calls accept an x-idempotency-key header, so retry them safely.


The user object

FieldTypeDescription
idstring (uuid)User id. Use it in every /users/{user_id} path.
user_idstring (uuid)Same value as id. Deprecated alias—read id.
objectstringAlways user.
external_idstring | nullYour own reference for the user, unique within your customer account.
customer_idstringYour customer account id.
account_typestringINDIVIDUAL or CORPORATE.
account_rolestringTHIRD.
account_purposestringTRADING or INVESTING.
first_namestringFirst name.
last_namestringLast name.
emailstringEmail, unique within your customer account.
phone_country_codestringISO 3166-1 alpha-2 country of the phone number.
phone_numberstringPhone number, unique within your customer account.
statusstringPENDING, VERIFIED, or SUSPENDED. See User statuses.
is_activebooleanfalse once the user is deactivated.
individualobject | nullKYC data. See The individual object.
created_atstringCreation time, UTC.
updated_atstringLast change, UTC.
created_bystring | nullId of the credential that created the user.
updated_bystring | nullWho last changed the user.

The individual object

FieldTypeDescription
birth_datestringDate of birth, YYYY-MM-DD. The user must be 18 or older.
residential_country_codestringCountry of residence (ISO 3166-1 alpha-2).
residential_addressstringStreet address of residence.
residential_citystringCity of residence.
residential_statestringState or province of residence.
residential_postal_codestringPostal code of residence.
id_typestringSSN or PASSPORT.
id_number_maskedstringIdentification number with all but the last four characters masked (*****6789). The full number is write-only.
id_country_codestringCountry that issued the ID (ISO 3166-1 alpha-2).

User statuses

  • PENDING—every new user starts here. A pending user can't receive a token.
  • VERIFIED—set by Activate User. Only verified, active users receive tokens.
  • SUSPENDED—set by Deactivate User. The user's token stops working until you activate them again.

You can't write status, account_type, or account_role directly; use the activate and deactivate actions.


Create User

POSThttps://api.sandbox.sovera.io/sovx/v1/users

Register a new end user under your customer account. The user starts as PENDING. Email, phone number, and external_id must each be unique within your customer account. The response is 201 Created, and the location header points at the new user (/v1/users/{id}).

Request Body

FieldTypeRequiredDescription
user_idstring (uuid v4)NoYour own UUID for the user. Generated when omitted. Must be unique.
external_idstringNoYour own reference for the user (max 255), unique within your customer account.
account_typestringYesINDIVIDUAL or CORPORATE.
account_rolestringYesTHIRD.
account_purposestringYesTRADING or INVESTING.
first_namestringYesFirst name (max 100).
last_namestringYesLast name (max 100).
emailstringYesValid email address.
phone_country_codestringYesISO 3166-1 alpha-2 country of the phone number (e.g. US).
phone_numberstringYesPhone number valid for phone_country_code (max 20).
individualobjectYesKYC data, all fields below required.
individual.birth_datestringYesDate of birth, YYYY-MM-DD. The user must be 18 or older.
individual.residential_country_codestringYesCountry of residence (ISO 3166-1 alpha-2).
individual.residential_addressstringYesStreet address.
individual.residential_citystringYesCity.
individual.residential_statestringYesState or province.
individual.residential_postal_codestringYesPostal code.
individual.id_typestringYesSSN or PASSPORT.
individual.id_numberstringYesIdentification number. It's returned masked.
individual.id_country_codestringYesCountry that issued the ID (ISO 3166-1 alpha-2).

Deprecated input, removed in the next release

Lowercase enum values (individual, third, investing, ssn) and individual.dob (use individual.birth_date) still work during this release.

{
  "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"
  }
}

Success 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"
  }
}

Error Response (409 Conflict):

{
  "success": false,
  "errors": [
    {
      "code": "EMAIL_ALREADY_IN_USE",
      "message": "User with this email already exists for this customer",
      "source": { "pointer": "/email" }
    }
  ],
  "meta": {
    "timestamp": "2026-10-12T14:30:00Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
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"
    }
  }'

Errors: 401 UNAUTHORIZED missing or invalid key · 403 FORBIDDEN not an owner key · 409 EMAIL_ALREADY_IN_USE / PHONE_ALREADY_IN_USE / EXTERNAL_ID_ALREADY_IN_USE / USER_ID_ALREADY_IN_USE (with source.pointer on the field) · 422 VALIDATION_ERROR one entry per invalid field · 429 RATE_LIMIT_EXCEEDED.


List Users

GEThttps://api.sandbox.sovera.io/sovx/v1/users

List your customer account's users, newest first. Deleted users never appear. The list uses cursor pagination and the filter grammar; an empty result is 200 with data: [].

Query Parameters

ParameterTypeRequiredDescription
filter[limit]integerNoPage size, 1–100 (default 25).
cursorstringNoOpaque cursor from meta.pagination.navigation.next or previous. Echo it as is.
filter[order]stringNocreated_at or updated_at, then ASC or DESC (default created_at DESC).
filter[where][status][eq|inq]stringNoPENDING, VERIFIED, SUSPENDED.
filter[where][account_type][eq|inq]stringNoINDIVIDUAL, CORPORATE.
filter[where][email][eq]stringNoExact email (case-insensitive).
filter[where][external_id][eq]stringNoExact external_id.
filter[where][created_at][gte|lte|between]stringNoUTC date-time ending in Z.

Up to six filter conditions per request. Any other field, operator, filter[skip], offset, or page returns 422 VALIDATION_ERROR naming the parameter in source.parameter.

GET https://api.sandbox.sovera.io/sovx/v1/users?filter[limit]=25&filter[where][status][eq]=VERIFIED

Success Response (200 OK):

{
  "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": "VERIFIED",
      "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:32:10Z",
      "created_by": "65f1c2a9e4b0a1d2c3f4a5b6",
      "updated_by": "65f1c2a9e4b0a1d2c3f4a5b6"
    }
  ],
  "meta": {
    "timestamp": "2026-10-12T14:35:00Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
    "pagination": {
      "records": { "limit": 25, "has_next": true, "has_previous": false },
      "navigation": {
        "next": "/v1/users?filter[limit]=25&filter[where][status][eq]=VERIFIED&cursor=3-gHUuzU-jSdf1bDLZKrZ0pN",
        "previous": null
      }
    }
  }
}
curl -g -X GET "https://api.sandbox.sovera.io/sovx/v1/users?filter[limit]=25&filter[where][status][eq]=VERIFIED" \
  -H "x-api-key: YOUR_OWNER_API_KEY"

Errors: 401 UNAUTHORIZED · 403 FORBIDDEN not an owner key · 422 VALIDATION_ERROR invalid filter, order, limit, or cursor · 429 RATE_LIMIT_EXCEEDED.


Get User

GEThttps://api.sandbox.sovera.io/sovx/v1/users/:user_id

Return one user by id. A user from another customer account, or a deleted one, returns 404.

GET https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}

Success Response (200 OK)—data is the user object, as in Create User.

Error Response (404 Not Found):

{
  "success": false,
  "errors": [
    {
      "code": "NOT_FOUND",
      "message": "User not found",
      "source": { "parameter": "user_id" }
    }
  ],
  "meta": {
    "timestamp": "2026-10-12T14:35:00Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
curl -X GET "https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}" \
  -H "x-api-key: YOUR_OWNER_API_KEY"

Errors: 401 UNAUTHORIZED · 403 FORBIDDEN · 404 NOT_FOUND · 429 RATE_LIMIT_EXCEEDED.


Update User

PATCHhttps://api.sandbox.sovera.io/sovx/v1/users/:user_id

Update a user's profile with merge semantics: only the fields you send change, and everything else keeps its value. KYC fields go under individual, and you can send just the ones that change. A new phone number is checked against the effective country (the one you send, or the stored one).

Request Body

All fields are optional.

FieldTypeDescription
first_namestringFirst name (max 100).
last_namestringLast name (max 100).
emailstringEmail, unique within your customer account.
phone_country_codestringISO 3166-1 alpha-2.
phone_numberstringPhone number (max 20), unique within your customer account.
account_purposestringTRADING or INVESTING.
individualobjectAny subset of birth_date, residential_country_code, residential_address, residential_city, residential_state, residential_postal_code, id_type, id_number, id_country_code.

Sending status, account_type, or account_role returns 422 VALIDATION_ERROR.

Deprecated, removed in the next release

PUT /v1/users/{user_id} still works as an alias of PATCH with the same merge behavior. The flat KYC names at the top level (dob, birth_date, residential_*, id_type, id_number, id_country_code) still map into individual; a nested value wins over its flat alias. Move to PATCH and the nested individual object.

{
  "phone_number": "3075550142",
  "individual": {
    "residential_city": "Laramie",
    "residential_postal_code": "82070"
  }
}

Success Response (200 OK)—data is the updated user object:

{
  "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": "3075550142",
    "status": "VERIFIED",
    "is_active": true,
    "individual": {
      "birth_date": "1990-10-15",
      "residential_country_code": "US",
      "residential_address": "123 Sovera Lane",
      "residential_city": "Laramie",
      "residential_state": "WY",
      "residential_postal_code": "82070",
      "id_type": "SSN",
      "id_number_masked": "*****6789",
      "id_country_code": "US"
    },
    "created_at": "2026-10-12T14:30:00Z",
    "updated_at": "2026-10-12T15:02:44Z",
    "created_by": "65f1c2a9e4b0a1d2c3f4a5b6",
    "updated_by": "65f1c2a9e4b0a1d2c3f4a5b6"
  },
  "meta": {
    "timestamp": "2026-10-12T15:02:44Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
curl -X PATCH "https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_OWNER_API_KEY" \
  -d '{
    "phone_number": "3075550142",
    "individual": {
      "residential_city": "Laramie",
      "residential_postal_code": "82070"
    }
  }'

Errors: 401 UNAUTHORIZED · 403 FORBIDDEN · 404 NOT_FOUND · 409 EMAIL_ALREADY_IN_USE / PHONE_ALREADY_IN_USE · 422 VALIDATION_ERROR (invalid field, phone not valid for the country, or a read-only field) · 429 RATE_LIMIT_EXCEEDED.


Delete User

DELETEhttps://api.sandbox.sovera.io/sovx/v1/users/:user_id

Soft-delete a user. The call revokes the user's access and refresh tokens at once, and later reads of the user return 404.

DELETE https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}

Success Response (204 No Content)—empty body.

curl -X DELETE "https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}" \
  -H "x-api-key: YOUR_OWNER_API_KEY"

Errors: 401 UNAUTHORIZED · 403 FORBIDDEN · 404 NOT_FOUND · 429 RATE_LIMIT_EXCEEDED.


Activate User

POSThttps://api.sandbox.sovera.io/sovx/v1/users/:user_id/activate

Move a user to VERIFIED and set is_active to true. Activate a new user before you issue their first token, and use this call to restore a suspended user. Send no body.

POST https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}/activate

Success Response (200 OK)—data is the user object with "status": "VERIFIED" and "is_active": true.

curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}/activate" \
  -H "x-api-key: YOUR_OWNER_API_KEY"

Errors: 401 UNAUTHORIZED · 403 FORBIDDEN · 404 NOT_FOUND · 429 RATE_LIMIT_EXCEEDED.


Deactivate User

POSThttps://api.sandbox.sovera.io/sovx/v1/users/:user_id/deactivate

Move a user to SUSPENDED and set is_active to false. The user's token stops working until you activate them again. Send no body.

POST https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}/deactivate

Success Response (200 OK)—data is the user object with "status": "SUSPENDED" and "is_active": false.

curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}/deactivate" \
  -H "x-api-key: YOUR_OWNER_API_KEY"

Errors: 401 UNAUTHORIZED · 403 FORBIDDEN · 404 NOT_FOUND · 429 RATE_LIMIT_EXCEEDED.


Events

Every webhook event Sovera produces for your customer account is stored, including events produced while no webhook endpoint was configured (those stay PENDING with status_reason: null and aren't retried). Read them here to reconcile missed deliveries or audit what was sent. The data of each event type is described in the event catalogue.

List Events

GEThttps://api.sandbox.sovera.io/sovx/v1/events

List your customer account's webhook events, newest first, with cursor pagination.

Query Parameters

ParameterTypeRequiredDescription
filter[limit]integerNoPage size, 1–100 (default 25).
cursorstringNoOpaque cursor from meta.pagination.navigation.
filter[order]stringNocreated_at ASC or created_at DESC (default).
filter[where][type][eq|inq]stringNoEvent type, e.g. user.deposit.received.
filter[where][delivery_status][eq|inq]stringNoPENDING, DELIVERED, SUSPENDED.
filter[where][resource_type][eq]stringNoResource type, e.g. DEPOSIT.
filter[where][resource_id][eq]stringNoId of the resource the event is about.
filter[where][created_at][gte|lte|between]stringNoUTC date-time ending in Z.

The event object

FieldTypeDescription
idstring (uuid)Event id.
event_idstring (uuid)Same value as id.
objectstringAlways event.
typestringDotted event type, e.g. user.deposit.received.
resource_typestring | nullType of the resource the event is about, UPPER_SNAKE.
resource_idstring | nullId of that resource.
sequence_numberinteger | nullPosition of the event among the events about the same resource (resource_type + resource_id), starting at 1.
dataobject | nullThe event payload, as delivered to your webhook, except that verification codes (data.code) are never returned.
delivery_statusstringPENDING (also while retries are pending), DELIVERED, or SUSPENDED.
status_reasonobject | null{code, message} when SUSPENDED: HTTP_<status> (your endpoint answered that status), TIMEOUT, or DELIVERY_FAILED.
attempt_countintegerDelivery attempts so far.
occurred_atstring | nullWhen the underlying change happened, UTC.
delivered_atstring | nullWhen your endpoint accepted the event, UTC.
created_atstring | nullWhen the event was recorded, UTC.
GET https://api.sandbox.sovera.io/sovx/v1/events?filter[where][delivery_status][eq]=SUSPENDED

Success Response (200 OK):

{
  "success": true,
  "data": [
    {
      "id": "3c9e1f7a-2b4d-4e6f-8a1c-5d7e9f0b2a4c",
      "event_id": "3c9e1f7a-2b4d-4e6f-8a1c-5d7e9f0b2a4c",
      "object": "event",
      "type": "user.deposit.received",
      "resource_type": "DEPOSIT",
      "resource_id": "96abb315-99d8-59e7-a333-1007dba37ad5",
      "sequence_number": 3,
      "data": {
        "wallet_id": "fd956820-0446-5e40-a0fe-8fff794b7fd6",
        "deposit_id": "96abb315-99d8-59e7-a333-1007dba37ad5",
        "amount": "0.05000000",
        "currency": "btc",
        "source_address": "bc1q9h7garjqzd4ps7pkkx6hf0rsk2xw6d6yvgxvq5",
        "tx_hash": "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90"
      },
      "delivery_status": "SUSPENDED",
      "status_reason": {
        "code": "HTTP_500",
        "message": "The webhook endpoint answered HTTP 500"
      },
      "attempt_count": 3,
      "occurred_at": "2026-10-12T14:28:03Z",
      "delivered_at": null,
      "created_at": "2026-10-12T14:28:03Z"
    }
  ],
  "meta": {
    "timestamp": "2026-10-12T14:30:00Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
    "pagination": {
      "records": { "limit": 25, "has_next": false, "has_previous": false },
      "navigation": { "next": null, "previous": null }
    }
  }
}
curl -g -X GET "https://api.sandbox.sovera.io/sovx/v1/events?filter[where][delivery_status][eq]=SUSPENDED" \
  -H "x-api-key: YOUR_OWNER_API_KEY"

Errors: 401 UNAUTHORIZED · 403 FORBIDDEN not an owner key · 422 VALIDATION_ERROR invalid filter, order, limit, or cursor · 429 RATE_LIMIT_EXCEEDED.


Next Steps

On this page