IBExWalletAPI

Begin

Minimum integration guide — sign up a user, sign them in, and start using the API in 5 minutes.

Everything you need to get your first user created and authenticated. This guide covers the minimum API calls required for a working integration.

Prerequisites

  • Your rpId (domain) must be registered with IBEx — what is rpId?
  • For local development, localhost works automatically (no registration needed)

Authentication flow overview

IBEx uses a two-step pattern for both sign-up and sign-in:

  1. GET — retrieve a challenge (WebAuthn options or KDF/email materials)
  2. POST — submit the signed challenge to complete the operation and receive JWT tokens
┌──────────┐         ┌──────────┐         ┌──────────┐
│  Client  │         │  IBEx    │         │ Browser  │
│  (your   │         │  API     │         │ WebAuthn │
│   app)   │         │          │         │          │
└────┬─────┘         └────┬─────┘         └────┬─────┘
     │                    │                    │
     │  GET /sign-up      │                    │
     │───────────────────>│                    │
     │  challenge options │                    │
     │<───────────────────│                    │
     │                    │                    │
     │  navigator.credentials.create(options)  │
     │────────────────────────────────────────>│
     │                credential               │
     │<────────────────────────────────────────│
     │                    │                    │
     │  POST /sign-up     │                    │
     │  { credential }    │                    │
     │───────────────────>│                    │
     │  { access_token,   │                    │
     │    refresh_token }  │                    │
     │<───────────────────│                    │
     └────────────────────┴────────────────────┘

Step 1 — Sign up a new user

1a. Get sign-up challenge

GET /v1.2/auth/sign-up?rpId=yourdomain.com

Response: WebAuthn credentialCreationOptions — pass this to the browser's navigator.credentials.create().

1b. Complete sign-up

Pass the credential returned by the browser to complete registration:

POST /v1.2/auth/sign-up
Content-Type: application/json
 
{
  "credential": {
    "id": "base64url...",
    "rawId": "base64url...",
    "type": "public-key",
    "response": {
      "attestationObject": "base64url...",
      "clientDataJSON": "base64url..."
    }
  }
}

Response: JWT tokens + user data:

{
  "access_token": "eyJ...",
  "refresh_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "subject": "externalUserId-uuid",
  "authMethod": "PASSKEY",
  "hasPasskey": true,
  "safeAddress": {
    "421614": "0xd676c6188195372EC269E9C2cAf815C56436A679"
  }
}

The user now has a Smart Account (Safe wallet) deployed on the default chain. Use access_token as Authorization: Bearer <token> for all subsequent API calls.


Step 2 — Sign in a returning user

2a. Get sign-in challenge

GET /v1.2/auth/sign-in?rpId=yourdomain.com

Response: WebAuthn credentialRequestOptions — pass this to navigator.credentials.get().

2b. Complete sign-in

POST /v1.2/auth/sign-in
Content-Type: application/json
 
{
  "credential": { ... },
  "includeBalance": true,
  "includeUserdata": true
}

Optional enrichment flags — avoid extra API calls by requesting data inline:

FlagWhat you get
includeBalanceAll token balances for the wallet (including zero balances)
includeTransactionsRecent transaction history
includeUserdataStored user preferences (language, email, etc.)

Response:

{
  "access_token": "eyJ...",
  "refresh_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "subject": "externalUserId-uuid",
  "authMethod": "PASSKEY",
  "hasPasskey": true,
  "balance": {
    "0x420c...3430": { "address": "0x420c...3430", "balance": "123.45" },
    "0xabcd...abcd": { "address": "0xabcd...abcd", "balance": "0" }
  },
  "userdata": { "preferences.language": "fr" }
}

Step 3 — Refresh the token

Access tokens expire after 1 hour. Use the refresh token to get a new pair:

POST /v1.2/auth/refresh
Content-Type: application/json
 
{
  "refresh_token": "eyJ..."
}

Response: New access_token + refresh_token.


What's next?

With JWT tokens in hand, you can now:

GoalEndpointSection
Get wallet addressesGET /v1.2/users/me/addressPrivacy → Me
Get balancesGET /v1.2/users/me/balancesPrivacy → Me
Transfer tokensPOST /v1.2/safes/operationsTransfer
Swap tokensGET /v1.2/safes/quotePOSTSwap
Real-time updateswss://host/wsWebSockets
Supply to AavePOST /v1.2/safes/operationsAave
Deposit to MorphoPOST /v1.2/safes/operationsMorpho
Hyperliquid vaultsPOST /v1.2/safes/operationsHyperliquid
DeFi positions & catalogGET /v1.2/users/me/poolsPools

Alternative authentication modes

This guide uses passkeys (default). IBEx also supports:

ModeUse caseDocs
wallet=kdfPIN/password-derived key (no biometric needed)Sign-in GET
wallet=emailEmail OTP + encrypted backupSign-in GET
wallet=7702External wallet (MetaMask, WalletConnect) via EIP-7702Sign-in GET

Minimal JavaScript example

import { startRegistration, startAuthentication } from '@simplewebauthn/browser';
 
const API = 'https://passkeys.ibex.fi';
 
// Sign up
async function signUp() {
  const opts = await fetch(`${API}/v1.2/auth/sign-up`).then(r => r.json());
  const credential = await startRegistration({ optionsJSON: opts.credentialCreationOptions });
  const tokens = await fetch(`${API}/v1.2/auth/sign-up`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ credential }),
  }).then(r => r.json());
  return tokens; // { access_token, refresh_token, safeAddress, ... }
}
 
// Sign in
async function signIn() {
  const opts = await fetch(`${API}/v1.2/auth/sign-in`).then(r => r.json());
  const credential = await startAuthentication({ optionsJSON: opts.credentialRequestOptions });
  const tokens = await fetch(`${API}/v1.2/auth/sign-in`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ credential, includeBalance: true }),
  }).then(r => r.json());
  return tokens; // { access_token, refresh_token, balance, ... }
}