This guide explains how to store and retrieve private user data using IBEX Safe's secure storage service. IBEX Safe is a regulated entity (VASP) compliant with GDPR, allowing your dApp to manage users' private data without ever storing it yourself.
The User Data API uses a generic proxy pattern to route requests to IBEX Safe. The IBEx.Fi API provides a transparent proxy endpoint that forwards requests to the IBEX Safe API, which handles the actual data storage and retrieval.
The proxy endpoints are:
:verb is userdata/external/:externalUserId):verb is userdata, validateEmail, confirmEmail, etc.)For user data operations, you call:
/userdata/userdata/external/:externalUserIdThe User Data API allows you to:
private. prefix (not returned on GET)private. are write-only and never returned on GET requestsTo store or update user data, use the POST /v1.1/ibexsafe/userdata endpoint. This routes through the generic proxy to IBEX Safe's /userdata endpoint.
Endpoint : POST /v1.1/ibexsafe/userdata (via generic proxy POST /v1.1/ibexsafe/:verb)
Headers :
Authorization: Bearer <access_token> (required)Content-Type: application/json (required)Body (JSON) :
externalUserId (string, required) : The external user identifier (from JWT)data (object, required) : Key/value pairs to store. Values can be:
string : Text values (e.g., email, names, addresses)boolean : Boolean flags (e.g., opt-in preferences)number : Numeric valuesnull : To delete a key (set to null)Request Example :
POST /v1.1/ibexsafe/userdata
Content-Type: application/json
Authorization: Bearer <access_token>
{
"externalUserId": "c76302cb-f845-40f4-9c56-29710323afba",
"data": {
"email": "jane.doe@foo.domain",
"firstName": "Jane",
"lastName": "Doe",
"language": "en",
"optin.newsletter": true,
"optin.walletAlerts": true,
"private.apiKey": "secret-api-key-12345"
}
}
Response (200 OK) :
{
"success": true
}
nullprivate. are write-only and will never be returned on GET requestsUser Profile Information :
{
"externalUserId": "c76302cb-f845-40f4-9c56-29710323afba",
"data": {
"email": "jane.doe@foo.domain",
"firstName": "Jane",
"lastName": "Doe",
"phone": "+33612345678",
"language": "en"
}
}
Opt-in Preferences :
{
"externalUserId": "c76302cb-f845-40f4-9c56-29710323afba",
"data": {
"optin.newsletter": true,
"optin.walletAlerts": true,
"optin.marketing": false
}
}
Private/Sensitive Data :
{
"externalUserId": "c76302cb-f845-40f4-9c56-29710323afba",
"data": {
"private.apiKey": "secret-key-12345",
"private.encryptionKey": "encrypted-data-here",
"private.customSecret": "sensitive-information"
}
}
private. are write-only. They can be stored but will never be returned when retrieving user data. Use this for sensitive information that should never be exposed via GET requests.
To retrieve stored user data, use the GET /v1.1/ibexsafe/userdata/external/:externalUserId endpoint. This routes through the generic proxy to IBEX Safe's /userdata/external/:externalUserId endpoint.
Endpoint : GET /v1.1/ibexsafe/userdata/external/:externalUserId (via generic proxy GET /v1.1/ibexsafe/:verb)
Headers :
Authorization: Bearer <access_token> (required)Path Parameters :
externalUserId (string, required) : The external user identifier (from JWT)Request Example :
GET /v1.1/ibexsafe/userdata/external/c76302cb-f845-40f4-9c56-29710323afba Authorization: Bearer <access_token>
Response (200 OK) :
{
"email": "jane.doe@foo.domain",
"firstName": "Jane",
"lastName": "Doe",
"language": "en",
"optin.newsletter": true,
"optin.walletAlerts": true
}
private. are not included in the response204 No ContentIf the GET endpoint returns 401 Unauthorized, you can use POST as a fallback:
Endpoint : POST /v1.1/ibexsafe/userdata
Body (JSON) :
{
"externalUserId": "c76302cb-f845-40f4-9c56-29710323afba",
"data": {}
}
Response (200 OK) :
{
"email": "jane.doe@foo.domain",
"firstName": "Jane",
"lastName": "Doe",
"language": "en",
"optin.newsletter": true,
"optin.walletAlerts": true
}
To update existing user data, use the same POST /v1.1/ibexsafe/userdata endpoint with partial data. Only the keys you include will be updated.
Request Example (Partial Update) :
POST /v1.1/ibexsafe/userdata
Content-Type: application/json
Authorization: Bearer <access_token>
{
"externalUserId": "c76302cb-f845-40f4-9c56-29710323afba",
"data": {
"language": "fr",
"optin.newsletter": false
}
}
Response (200 OK) :
{
"success": true
}
To delete a key, set its value to null:
POST /v1.1/ibexsafe/userdata
Content-Type: application/json
Authorization: Bearer <access_token>
{
"externalUserId": "c76302cb-f845-40f4-9c56-29710323afba",
"data": {
"phone": null,
"optin.marketing": null
}
}
You can optionally include user data in the sign-in response by setting includeUserdata: true in the sign-in request body:
Request Example :
POST /v1.2/auth/sign-in
Content-Type: application/json
{
"credential": { ... },
"includeUserdata": true
}
Response (200 OK) :
{
"access_token": "...",
"refresh_token": "...",
"safeAddress": { ... },
"userdata": {
"email": "jane.doe@foo.domain",
"firstName": "Jane",
"lastName": "Doe",
"language": "en",
"optin.newsletter": true,
"optin.walletAlerts": true
}
}
includeUserdata is not set or is false, the userdata field will not be included in the response.
If the JWT is missing or invalid, you will receive a 401 error. Make sure to include a valid Authorization header.
If the request body is malformed or externalUserId doesn't match the authenticated user, you will receive a 400 error.
If the user data doesn't exist, GET requests return 204 No Content (not 404).
POST /v1.1/ibexsafe/userdata with user dataGET /v1.1/ibexsafe/userdata/external/:externalUserId to get stored dataPOST /v1.1/ibexsafe/userdata with partial updates as neededincludeUserdata: true in sign-in requestsprivate. are write-only and never returnednull to delete itexternalUserId must match the authenticated user from the JWTThis section provides detailed technical information for AI systems integrating the IBEX User Data storage flow, including architecture patterns, data models, and implementation details.
Generic Proxy Pattern
GET /v1.1/ibexsafe/:verb and POST /v1.1/ibexsafe/:verb:verb is userdata or userdata/external/:externalUserId, so you call POST /v1.1/ibexsafe/userdata or GET /v1.1/ibexsafe/userdata/external/:externalUserIdPOST /v1.1/ibexsafe/userdata
POST /v1.1/ibexsafe/:verb with verb=userdataPOST /userdataexternalUserId must match authenticated user (validated by IBEx.Fi API)string, boolean, number, null - all forwarded to IBEX Safeprivate. prefix are write-only (handled by IBEX Safe)null to delete (handled by IBEX Safe)GET /v1.1/ibexsafe/userdata/external/:externalUserId
GET /v1.1/ibexsafe/:verb with verb=userdata/external/:externalUserIdGET /userdata/external/:externalUserIdprivate. prefix are excluded (handled by IBEX Safe)204 No Content if no data exists (as returned by IBEX Safe)data: {} can be used if GET returns 401 (handled by IBEX Safe)User Data Structure:
{
"externalUserId": "string (UUID)",
"data": {
"key1": "string | boolean | number | null",
"key2": "string | boolean | number | null",
"private.secretKey": "string | boolean | number | null" // Write-only
}
}
Common Keys (Examples):
email : User email addressfirstName : User first namelastName : User last namelanguage : Preferred language (e.g., "en", "fr")phone : Phone numberoptin.newsletter : Newsletter opt-in (boolean)optin.walletAlerts : Wallet alerts opt-in (boolean)optin.marketing : Marketing opt-in (boolean)private.apiKey : Private API key (write-only)private.encryptionKey : Private encryption key (write-only)Private Keys:
private. are write-onlyGDPR Compliance:
Sign-In Integration:
includeUserdata: true in sign-in request bodyuserdata fieldWebSocket Integration:
user_dataexternalUserIdsrc/routes/v1.1/ibexsafe.ts : User data endpoints (POST/GET proxies)src/clients/IbexSafe.ts : IBEX Safe API clientsrc/routes/v1/auth.ts : Sign-in integration with includeUserdataPOST /v1.1/ibexsafe/userdata with { externalUserId, data: { key: value } }GET /v1.1/ibexsafe/userdata/external/:externalUserIdprivate.* keys from responsePOST /v1.1/ibexsafe/userdata with partial updatesnull in update request