This guide explains how to deploy a wallet for your users, covering signup, signin, refresh token, and wallet recovery.
Sign up allows a new user to create an account. The v1.2 API supports three different signup scenarios:
Endpoint : GET /v1.2/auth/sign-up?passkeys=FALSE
Query Parameters :
passkeys (string, required) : Set to FALSE to skip passkey creationRequest Example :
GET /v1.2/auth/sign-up?passkeys=FALSE
Response (200 OK) :
{
"userId": "<userId>",
"externalUserId": "<externalUserId>",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"issuer": "foo.domain",
"audience": "foo.domain",
"subject": "<externalUserId>",
"roles": ["USER"],
"hasPasskey": false,
"emailValidationRequired": false
}
externalUserId and a credential.
Endpoint : GET /v1.2/auth/sign-up?passkeys=FALSE&email=user@example.com
Query Parameters :
passkeys (string, required) : Set to FALSEemail (string, required) : Email address for validationRequest Example :
GET /v1.2/auth/sign-up?passkeys=FALSE&email=user@example.com
Response (200 OK) :
{
"userId": "<userId>",
"externalUserId": "<externalUserId>",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"emailValidationRequired": true
}
Complete Email Validation (POST) :
POST /v1.2/auth/sign-up
Content-Type: application/json
{
"emailCode": "123456",
"externalUserId": "<externalUserId>"
}
Endpoint : GET /v1.2/auth/sign-up or GET /v1.2/auth/sign-up?passkeys=TRUE
Optional Parameters (query string) :
passkeys (string, optional) : TRUE (default) to generate WebAuthn challengeemail (string, optional) : Email address for validation (optional)user.name (string) : Username for WebAuthn. Example: "jane.doe@foo.domain"user.displayname (string) : Display name for WebAuthn. Example: "Jane Doe"userName (string) : Alias for user.nameuserDisplayName (string) : Alias for user.displaynameRequest Example :
GET /v1.2/auth/sign-up?user.name=jane.doe@foo.domain&user.displayname=Jane%20Doe&email=user@example.com
Response (200 OK) :
{
"credentialRequestOptions": {
"rp": { "id": "foo.domain", "name": "foo.domain" },
"user": {
"id": "<base64url>",
"name": "foo.domain <shortId>",
"displayName": "foo.domain <shortId>"
},
"challenge": "<base64url>",
"pubKeyCredParams": [{ "alg": -7, "type": "public-key" }],
"authenticatorSelection": {
"residentKey": "preferred",
"userVerification": "preferred"
},
"attestation": "none",
"timeout": 60000
},
"emailValidationRequired": true
}
credentialRequestOptions object. It will be needed for the next step. If emailValidationRequired is true, you can optionally validate the email in the POST request.
Endpoint : POST /v1.2/auth/sign-up
Body (JSON) :
credential (object, required if passkeys=TRUE) : WebAuthn credential obtained via navigator.credentials.create() with the options from step 1emailCode (string, optional) : Email validation code (if email was provided in GET request)externalUserId (string, optional) : ExternalUserId from GET /sign-up (for deferred passkey creation)chainIds (array of numbers, optional) : List of chain IDs for multi-chain deployment. The effective chain (from X-Blockchain-Id or ?blockchainId) is automatically included if missingchainId (number, optional) : Single chain ID (alternative to chainIds)keyName (string, optional) : Passkey display namekeyDisplayName (string, optional) : Passkey display label for the userRequest Example (Passkey only) :
POST /v1.2/auth/sign-up
Content-Type: application/json
{
"credential": {
"id": "AVZs0qRCBSmfThZWu37g...",
"rawId": "AVZs0qRCBSmfThZWu37g...",
"type": "public-key",
"response": {
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVh...",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoi..."
}
},
"chainIds": [421614],
"keyName": "my-passkey",
"keyDisplayName": "My Passkey"
}
Request Example (Passkey + Email validation) :
POST /v1.2/auth/sign-up
Content-Type: application/json
{
"credential": {
"id": "AVZs0qRCBSmfThZWu37g...",
"rawId": "AVZs0qRCBSmfThZWu37g...",
"type": "public-key",
"response": {
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVh...",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoi..."
}
},
"emailCode": "123456",
"chainIds": [421614]
}
Request Example (Deferred passkey - add passkey to existing account) :
POST /v1.2/auth/sign-up
Content-Type: application/json
{
"externalUserId": "<externalUserId-from-previous-signup>",
"credential": {
"id": "AVZs0qRCBSmfThZWu37g...",
"rawId": "AVZs0qRCBSmfThZWu37g...",
"type": "public-key",
"response": {
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVh...",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoi..."
}
},
"chainIds": [421614]
}
Response (200 OK) :
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"issuer": "foo.domain",
"audience": "foo.domain",
"subject": "<externalUserId>",
"roles": ["USER"],
"safeAddress": {
"421614": "0xd676c6188195372EC269E9C2cAf815C56436A679"
},
"chainId": 421614,
"keyName": "my-passkey",
"keyDisplayName": "My Passkey"
}
access_token : JWT to authenticate subsequent requests (duration: ~1h)refresh_token : JWT to obtain a new access_token (store securely)subject : Unique externalUserId for this user for this rpIdsafeAddress : Deployed Safe addresses by chainIdSign in allows an existing user to authenticate. The v1.2 API supports three different sign-in scenarios:
Endpoint : GET /v1.2/auth/sign-in
Optional Parameters (query string) :
chainId (number) : Target chain ID for Safe operationsRequest Example :
GET /v1.2/auth/sign-in?chainId=421614
Response (200 OK) :
{
"credentialRequestOptions": {
"challenge": "<base64url>",
"rpId": "foo.domain",
"userVerification": "required",
"timeout": 60000
}
}
Endpoint : GET /v1.2/auth/sign-in?email=user@example.com&externalUserId=<externalUserId>
Query Parameters :
email (string, required) : Email addressexternalUserId (string, required) : ExternalUserId of the userRequest Example :
GET /v1.2/auth/sign-in?email=user@example.com&externalUserId=abc-123-def
Response (200 OK) :
{
"emailCodeSent": true,
"email": "user@example.com",
"message": "A validation code has been sent to your email"
}
Endpoint : GET /v1.2/auth/sign-in?externalUserId=<externalUserId>
Query Parameters :
externalUserId (string, required) : ExternalUserId of the userRequest Example :
GET /v1.2/auth/sign-in?externalUserId=abc-123-def
Response (200 OK) :
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"issuer": "foo.domain",
"audience": "foo.domain",
"subject": "<externalUserId>",
"roles": ["USER"],
"authMethod": "EXTERNAL_USER_ID",
"hasPasskey": false
}
Endpoint : POST /v1.2/auth/sign-in
Body (JSON) :
credential (object, optional) : WebAuthn credential obtained via navigator.credentials.get() (for passkey authentication)emailCode (string, optional) : Email validation code (for email authentication)email (string, optional) : Email address (required if emailCode provided)externalUserId (string, optional) : ExternalUserId (required for email-only sign-in)chainId (number, optional) : Target chain ID for Safe operationsincludeBalance (boolean, optional) : Include current balance in HTTP response (for non-WebSocket clients)includeTransactions (boolean, optional) : Include full transaction history in HTTP responseincludeUserdata (boolean, optional) : Include IBEX Safe userData in HTTP responseRequest Example (Passkey) :
POST /v1.2/auth/sign-in
Content-Type: application/json
{
"credential": {
"id": "AVZs0qRCBSmfThZWu37g...",
"rawId": "AVZs0qRCBSmfThZWu37g...",
"type": "public-key",
"response": {
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAQ...",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoi...",
"signature": "MEUCIQC..."
}
},
"chainId": 421614,
"includeBalance": true,
"includeUserdata": true
}
Request Example (Email) :
POST /v1.2/auth/sign-in
Content-Type: application/json
{
"emailCode": "123456",
"email": "user@example.com",
"externalUserId": "abc-123-def"
}
Response (200 OK) :
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"issuer": "foo.domain",
"audience": "foo.domain",
"subject": "<externalUserId>",
"roles": ["USER"],
"authMethod": "PASSKEY",
"hasPasskey": true,
"safeAddress": {
"421614": "0xd676c6188195372EC269E9C2cAf815C56436A679"
},
"chainId": 421614,
"keyName": "my-passkey",
"keyDisplayName": "My Passkey",
"balance": { /* BCReader balances si includeBalance=true */ },
"transactions": { /* Historique si includeTransactions=true */ },
"userdata": { /* IBEX Safe userdata si includeUserdata=true */ }
}
access_token, refresh_token, and subject.
The refresh token allows you to obtain a new access_token before expiration (duration: ~1h).
Endpoint : POST /v1.2/auth/refresh
/v1.2/auth/refresh, /v1.1/auth/refresh, or /v1.2/auth/refresh.
Body (JSON) :
refresh_token (string, required) : Refresh token (JWT) obtained during sign-in or sign-upRequest Example :
POST /v1.2/auth/refresh
Content-Type: application/json
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response (200 OK) :
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"issuer": "foo.domain",
"audience": "foo.domain",
"subject": "<externalUserId>",
"roles": ["USER"]
}
access_token AND a new refresh_token. Replace both in your local storage.
Recovery allows a user to configure a wallet recovery mechanism in case of access loss.
Endpoint : POST /v1.2/safes/operations
Headers :
Authorization: Bearer <access_token> (required)Body (JSON) :
safeAddress (string, required) : Safe address for which to enable recoveryoperations (array, required) : Array containing an operation of type ENABLE_RECOVERYENABLE_RECOVERY Operation :
type (string, required) : "ENABLE_RECOVERY"newOwners (array of strings, required) : List of new owner addresses (0x... format)threshold (number, required) : Required signature threshold (usually 1)firstName (string, optional) : First name for personal datalastName (string, optional) : Last name for personal databirthDate (string, optional) : Birth date (YYYY-MM-DD format)birthCity (string, optional) : Birth citybirthCountry (string, optional) : Birth countryRequest Example :
POST /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>
{
"safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
"operations": [
{
"type": "ENABLE_RECOVERY",
"newOwners": ["0x303f215950f7B07Ae45FD98590d503E0242E7de3"],
"threshold": 1,
"firstName": "Jane",
"lastName": "Doe",
"birthDate": "1990-01-01",
"birthCity": "Paris",
"birthCountry": "France"
}
]
}
Response (200 OK) :
{
"credentialRequestOptions": {
"challenge": "<base64url_encoded_userOpHash>",
"rpId": "foo.domain",
"userVerification": "required"
}
}
credentialRequestOptions, you must:
navigator.credentials.get() with these options to obtain a credentialPUT /v1.2/safes/operations request with the credential to finalize activationEndpoint : PUT /v1.2/safes/operations
Body (JSON) :
credential (object, required) : WebAuthn credential obtained with the options from the previous stepRequest Example :
PUT /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>
{
"credential": {
"id": "AVZs0qRCBSmfThZWu37g...",
"rawId": "AVZs0qRCBSmfThZWu37g...",
"type": "public-key",
"response": {
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAQ...",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoi...",
"signature": "MEUCIQC..."
}
}
}
To cancel an ongoing recovery, use the CANCEL_RECOVERY operation:
Request Example :
POST /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>
{
"safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
"operations": [
{
"type": "CANCEL_RECOVERY"
}
]
}
This section provides detailed technical information for AI systems integrating the IBEX API, including architecture patterns, data models, and implementation details.
The IBEX API uses a multi-layered architecture:
The authentication and wallet system uses the following entity relationships:
Domain (rpId)
└── ExternalUser (id, rpId, userId)
└── Signer (id, externalUserId, type: PASSKEY)
└── Safe (address, blockchainId, signerId, threshold)
├── SafeOperation (userOpHash, status, safeAddress, blockchainId)
└── Iban (safeAddress, blockchainId, status)
User (id, ky, lock)
└── ExternalUser (id, rpId, userId)
└── Login (id, userId, signerId, safeAddress, type, event)
Key Relationships:
User : Internal user entity (one-to-many with ExternalUser)ExternalUser : Domain-scoped user identity (unique per rpId, linked to User)Signer : WebAuthn credential/passkey (linked to ExternalUser, identified by credential ID)Safe : Smart contract wallet (composite key: address + blockchainId, linked to Signer)SafeOperation : User operation for Safe transactions (status: CREATED → SIGNED → EXECUTED → CONFIRMED)Login : Authentication event log (tracks sign-in/sign-up events)Step 1: GET /v1.2/auth/sign-up
User and ExternalUser records immediatelypasskeys=FALSE: Returns JWT tokens directly (no WebAuthn challenge)passkeys=TRUE: Generates WebAuthn registration options using generateRegistrationOptions() from @simplewebauthn/serveremail provided: Calls validateEmail() to send validation codeStep 2: POST /v1.2/auth/sign-up
verifyRegistrationResponse()User record (if new user, UUID generated)ExternalUser record (UUID, linked to User and Domain via rpId)Signer record (credential ID as primary key, linked to ExternalUser)Safe records (one per chainId, computed address via Safe4337Pack)Login record (SIGNER type, SIGNUP event)access_token : JWT with issuer=rpId, audience=rpId, subject=externalUserId, expires_in=3600srefresh_token : JWT containing challenge, used to refresh access_tokenStep 1: GET /v1.2/auth/sign-in
externalUserId provided: Returns JWT tokens directly (no challenge)email + externalUserId provided: Sends email validation codeChallengeKey.AUTHENTICATION (if passkey flow)Step 2: POST /v1.2/auth/sign-in
verifyAuthenticationResponse()Login record (SIGNER type, SIGNIN event, linked to User, Signer, and Safe)keyName, keyDisplayName, userOpHash, transactionHashPOST /v1.2/auth/refresh
Login record (REFRESH_TOKEN type)ENABLE_RECOVERY Operation
MODULE_PROXY_FACTORY to deploy RecoveryModule via CREATE2credentialRequestOptions with challenge = userOpHash (base64url encoded)CANCEL_RECOVERY Operation
cancelRecovery() on RecoveryModuleAccess Token Payload:
{
"iss": "foo.domain", // rpId (issuer)
"aud": "foo.domain", // rpId (audience)
"sub": "<externalUserId>", // ExternalUser.id
"roles": ["USER"], // User role
"iat": 1234567890, // Issued at
"exp": 1234571490 // Expires at (iat + 3600s)
}
Refresh Token Payload:
{
"challenge": "<base64url>", // Challenge stored in Redis
"iat": 1234567890,
"exp": 1234571490
}
Address Computation:
Multi-Chain Support:
SafeOperation Status Flow:
CREATED : Operation created, awaiting passkey signatureSIGNED : Passkey signature received, operation ready for bundlerEXECUTED : Bundler submitted userOp to blockchain, awaiting confirmationCONFIRMED : Sufficient block confirmations received (typically 1-2 blocks)FAILED : Operation failed (bundler error, insufficient funds, etc.)Status Transitions:
Connection Flow:
wss://api.ibex.fi/ws{ type: "auth", token: "<JWT>", clientName: "..." }src/routes/v1.2/auth.ts : Enhanced authentication endpoints (v1.2) with multiple auth methodssrc/routes/v1/auth.ts : Legacy authentication endpoints (v1) for backward compatibilitysrc/passkey.ts : WebAuthn verification, Safe deployment, multi-chain logicsrc/routes/v1/safesOperations.ts : Safe operation preparation and executionsrc/routes/websocket.ts : WebSocket connection handling and message routingsrc/clients/IbexSafe.ts : Email validation functions (validateEmail, confirmEmail)prisma/schema.prisma : Complete database schema definition