WebSocket - IBEx.Fi API Integration Guide

View Flow Diagram

Overview

The IBEx.Fi API provides WebSocket connections to receive real-time updates for wallet balances, transactions, and user data. This eliminates the need for constant polling and provides instant notifications when blockchain events occur.

💡 Benefits of WebSocket :

1. WebSocket Connection

Connect to the WebSocket endpoint using the standard WebSocket protocol.

Endpoint : ws://host/ws or wss://host/ws (secure)

Connection Example :

const ws = new WebSocket('wss://api.ibex.fi/ws');

ws.onopen = () => {
  console.log('✅ WebSocket connected');
  // Authentication will be sent next
};
💡 Note : Use wss:// (secure WebSocket) in production. The WebSocket connection is established first, then authentication happens via a JSON message.

2. Authentication

After the WebSocket connection is established, authenticate using a JWT token (the same token used for HTTP API calls).

Authentication Message (Client → Server) :

{
  "type": "auth",
  "token": "eyJhbGciOi...",  // JWT token from POST /v1.2/auth/sign-in
  "clientName": "My App"      // Optional: useful for monitoring
}

JavaScript Example :

ws.onopen = () => {
  // Send authentication immediately after connection opens
  ws.send(JSON.stringify({
    type: 'auth',
    token: accessToken,  // Your JWT access token
    clientName: 'My Wallet App'
  }));
};

Authentication Success Response (Server → Client) :

{
  "type": "auth_success",
  "data": {
    "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "message": "Connected to real-time updates"
  },
  "timestamp": "2025-01-31T12:00:00.000Z"
}

Authentication Error Response (Server → Client) :

{
  "type": "auth_error",
  "data": {
    "message": "JWT token expired, please refresh your token",
    "error_code": "TOKEN_EXPIRED",
    "context": "auth_process"
  },
  "timestamp": "2025-01-31T12:00:00.000Z"
}
⚠️ Important Points :

3. Automatic Initial Data

Immediately after successful authentication, the server automatically sends the following data without any additional requests:

  1. auth_success : Confirmation of successful authentication
  2. balance_data : Current wallet balance
  3. transaction_data : Recent transaction history (last 50 transactions)
  4. chainid_data : Default and supported chain IDs
  5. recovery_data : Recovery status for the Safe
  6. user_data : User information, signers, and Safe addresses

3.1. Balance Data

Message Type : balance_data

{
  "type": "balance_data",
  "data": {
    "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "balance": {
      "balances": [
        {
          "token": "0xEURE12345678901234567890123456789012345678",
          "symbol": "EURE",
          "balance": "123.456789",
          "decimals": 18
        }
      ]
    }
  },
  "timestamp": "2025-01-31T12:00:00.000Z"
}

3.2. Transaction Data

Message Type : transaction_data

{
  "type": "transaction_data",
  "data": {
    "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "transactions": {
      "data": [
        {
          "hash": "0xabcdef1234567890...",
          "blockNumber": 12345678,
          "timestamp": "2025-01-31T11:59:00.000Z",
          "from": "0xabc...",
          "to": "0x1234...",
          "value": "100.0",
          "direction": "IN",
          "tokenSymbol": "EURE"
        }
      ]
    }
  },
  "timestamp": "2025-01-31T12:00:00.000Z"
}

3.3. Chain ID Data

Message Type : chainid_data

{
  "type": "chainid_data",
  "data": {
    "defaultChainId": 421614,
    "supportedChainIds": [421614]
  },
  "timestamp": "2025-01-31T12:00:01.000Z"
}

3.4. Recovery Data

Message Type : recovery_data

{
  "type": "recovery_data",
  "data": {
    "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "recoveryEnabled": false,
    "recoveryAddress": null,
    "delay": null,
    "pendingRecovery": false,
    "canExecute": false,
    "executeAfter": null
  },
  "timestamp": "2025-01-31T12:00:01.000Z"
}

3.5. User Data

Message Type : user_data

{
  "type": "user_data",
  "data": {
    "id": "ext_user_123",
    "ky": "5",
    "signers": [
      {
        "id": "signer_id",
        "safes": [
          {
            "address": "0xd676c6188195372EC269E9C2cAf815C56436A679",
            "threshold": 1,
            "iban": {
              "chainId": 421614,
              "iban": "FR76...",
              "bic": "CREDFRPP"
            }
          }
        ]
      }
    ]
  },
  "timestamp": "2025-01-31T12:00:01.000Z"
}

4. Real-Time Updates

After authentication, you automatically receive real-time updates for your Safe address. No additional subscription is required.

4.1. Balance Updates

Message Type : balance_update

Sent automatically when the wallet balance changes (new transactions, token transfers, etc.).

{
  "type": "balance_update",
  "data": {
    "address": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "balance": "123.456789",
    "updated_at": "2025-01-31T12:00:00.000Z"
  },
  "timestamp": "2025-01-31T12:00:00.000Z"
}

4.2. New Transaction Notifications

Message Type : new_transaction

Sent automatically when a new transaction is detected for your Safe address.

{
  "type": "new_transaction",
  "data": {
    "address": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "newTransaction": {
      "hash": "0xabcdef1234567890...",
      "blockNumber": 12345678,
      "timestamp": "2025-01-31T12:00:00.000Z",
      "from": "0xabc...",
      "to": "0xdef...",
      "tokenAddress": "0xEURE12345678901234567890123456789012345678",
      "tokenType": "ERC20",
      "tokenSymbol": "EURE",
      "tokenName": "Monerium EUR emoney",
      "value": "100.0",
      "direction": "IN"
    },
    "recentTransactions": [ /* recent transactions (optional) */ ],
    "transactionCount": 1,
    "historyLimit": 5
  },
  "timestamp": "2025-01-31T12:00:00.000Z"
}

4.3. IBAN Status Updates

Message Type : user.iban.updated

Sent automatically when the IBAN status changes (e.g., from PENDING to VERIFIED).

{
  "type": "user.iban.updated",
  "data": {
    "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "iban": "DE89 3704 0044 0532 0130 00",
    "previousState": "PENDING",
    "newState": "VERIFIED",
    "updatedAt": "2025-08-01T07:36:04.083Z"
  },
  "timestamp": "2025-08-01T07:36:04.083Z"
}

4.4. KYC Status Updates

Message Type : user.ky.updated

Sent automatically when the KYC status changes (e.g., from PENDING to VERIFIED).

{
  "type": "user.ky.updated",
  "data": {
    "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "previousKyc": "PENDING",
    "newKyc": "VERIFIED",
    "updatedAt": "2025-08-01T07:36:04.083Z"
  },
  "timestamp": "2025-08-01T07:36:04.083Z"
}

5. Request Data on Demand

You can also request balance or transaction data on demand by sending specific message types.

5.1. Request Balance

Message Type (Client → Server) : get_balance

{
  "type": "get_balance",
  "requestId": "req-123"  // Optional: for tracking responses
}

Response (Server → Client) :

{
  "type": "balance_data",
  "data": {
    "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "balance": { /* balance data */ },
    "requestId": "req-123"  // Echoed if provided
  },
  "timestamp": "2025-01-31T12:00:00.000Z"
}

5.2. Request Transactions

Message Type (Client → Server) : get_transactions

{
  "type": "get_transactions",
  "params": {
    "startDate": "2025-01-01",  // Optional
    "endDate": "2025-01-31",    // Optional
    "limit": 50,                 // Optional
    "page": 1                    // Optional
  },
  "requestId": "req-456"  // Optional: for tracking responses
}

Response (Server → Client) :

{
  "type": "transaction_data",
  "data": {
    "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
    "transactions": { /* transaction data */ },
    "requestId": "req-456"  // Echoed if provided
  },
  "timestamp": "2025-01-31T12:00:00.000Z"
}

6. Complete Implementation Example

class IBEXWebSocket {
  constructor(apiUrl, jwtToken) {
    this.apiUrl = apiUrl;
    this.jwtToken = jwtToken;
    this.ws = null;
    this.reconnectInterval = null;
    this.maxReconnectAttempts = 5;
    this.reconnectAttempts = 0;
    this.isAuthenticated = false;
  }

  connect() {
    try {
      console.log('🔌 Connecting to IBEX WebSocket...');
      this.ws = new WebSocket(this.apiUrl);

      this.ws.onopen = () => {
        console.log('✅ WebSocket connected');
        this.reconnectAttempts = 0;
        // Send authentication immediately
        this.ws.send(JSON.stringify({
          type: 'auth',
          token: this.jwtToken,
          clientName: 'My Wallet App'
        }));
      };

      this.ws.onmessage = (event) => {
        const message = JSON.parse(event.data);
        this.handleMessage(message);
      };

      this.ws.onclose = (event) => {
        console.log(`👋 Connection closed: ${event.code} ${event.reason}`);
        this.isAuthenticated = false;
        this.scheduleReconnect();
      };

      this.ws.onerror = (error) => {
        console.error('❌ WebSocket error:', error);
      };

    } catch (error) {
      console.error('❌ Connection failed:', error);
      this.scheduleReconnect();
    }
  }

  handleMessage(message) {
    console.log('📥 Received:', message.type);
    
    switch(message.type) {
      case 'auth_success':
        console.log('🔐 Authenticated, Safe:', message.data.safeAddress);
        this.isAuthenticated = true;
        this.onAuthSuccess(message.data);
        break;
        
      case 'auth_error':
        console.error('❌ Authentication failed:', message.data.message);
        this.onAuthError(message.data);
        break;
        
      case 'balance_data':
        console.log('💰 Balance data:', message.data.balance);
        this.onBalanceData(message.data);
        break;
        
      case 'balance_update':
        console.log('💰 Balance updated:', message.data.balance);
        this.onBalanceUpdate(message.data);
        break;
        
      case 'transaction_data':
        console.log('📊 Transaction data:', message.data.transactions);
        this.onTransactionData(message.data);
        break;
        
      case 'new_transaction':
        console.log('🔄 New transaction:', message.data.newTransaction.hash);
        this.onNewTransaction(message.data);
        break;
        
      case 'chainid_data':
        console.log('⛓️ Chain IDs:', message.data);
        this.onChainIdData(message.data);
        break;
        
      case 'recovery_data':
        console.log('🔒 Recovery status:', message.data);
        this.onRecoveryData(message.data);
        break;
        
      case 'user_data':
        console.log('👤 User data:', message.data);
        this.onUserData(message.data);
        break;
        
      case 'user.iban.updated':
        console.log('🏦 IBAN updated:', message.data);
        this.onIbanUpdated(message.data);
        break;
        
      case 'user.ky.updated':
        console.log('✅ KYC updated:', message.data);
        this.onKycUpdated(message.data);
        break;
        
      case 'error':
        console.error('❌ Error:', message.data);
        this.onError(message.data);
        break;
        
      default:
        console.log('📝 Unknown message type:', message.type);
    }
  }

  // Request balance on demand
  requestBalance() {
    if (this.isAuthenticated && this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'get_balance',
        requestId: `balance-${Date.now()}`
      }));
    }
  }

  // Request transactions on demand
  requestTransactions(params = {}) {
    if (this.isAuthenticated && this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'get_transactions',
        params: {
          startDate: params.startDate || '2025-01-01',
          endDate: params.endDate || new Date().toISOString().split('T')[0],
          limit: params.limit || 50,
          page: params.page || 1
        },
        requestId: `transactions-${Date.now()}`
      }));
    }
  }

  scheduleReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('❌ Max reconnect attempts reached');
      return;
    }

    const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
    this.reconnectAttempts++;
    
    console.log(`🔄 Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
    
    this.reconnectInterval = setTimeout(() => {
      this.connect();
    }, delay);
  }

  // Override these methods in your implementation
  onAuthSuccess(data) {
    // Handle successful authentication
  }

  onAuthError(data) {
    // Handle authentication error (e.g., refresh token)
  }

  onBalanceData(data) {
    // Handle initial balance data
  }

  onBalanceUpdate(data) {
    // Handle balance updates
  }

  onTransactionData(data) {
    // Handle initial transaction data
  }

  onNewTransaction(data) {
    // Handle new transaction notifications
  }

  onChainIdData(data) {
    // Handle chain ID data
  }

  onRecoveryData(data) {
    // Handle recovery status
  }

  onUserData(data) {
    // Handle user data
  }

  onIbanUpdated(data) {
    // Handle IBAN status updates
  }

  onKycUpdated(data) {
    // Handle KYC status updates
  }

  onError(data) {
    // Handle errors
  }

  disconnect() {
    if (this.reconnectInterval) {
      clearTimeout(this.reconnectInterval);
    }
    if (this.ws) {
      this.ws.close();
    }
  }
}

// Usage
const ws = new IBEXWebSocket('wss://api.ibex.fi/ws', 'your-jwt-token');
ws.connect();

// Request data on demand
ws.requestBalance();
ws.requestTransactions({ limit: 20 });

7. Error Handling

⚠️ Best Practices :

8. Keep-Alive (Ping/Pong)

The server automatically sends a ping every 30 seconds to keep the connection alive. Standard WebSocket clients automatically respond with a pong — no client action is required.

💡 Note : If the connection is closed unexpectedly, implement automatic reconnection with exponential backoff. The server will automatically resend initial data after re-authentication.

Technical Deep Dive - For AI Integration

This section provides detailed technical information for AI systems integrating the IBEX WebSocket flow, including architecture patterns, message formats, and implementation details.

WebSocket Architecture

Connection Flow :

  1. Client establishes WebSocket connection to /ws
  2. Server accepts connection (no authentication required initially)
  3. Client sends authentication message with JWT token
  4. Server validates JWT and extracts Safe address
  5. Server subscribes to BCReader WebSocket for Safe address updates
  6. Server sends initial data (balance, transactions, chain, recovery, user data)
  7. Server forwards real-time updates from BCReader to client

Message Types

Client → Server Messages :

Server → Client Messages :

Key Implementation Files

Complete Flow - Technical Sequence

  1. WebSocket connection : Client connects to wss://api.ibex.fi/ws
  2. Connection accepted : Server accepts connection (temporary connection ID created)
  3. Authentication message : Client sends { type: 'auth', token: '...', clientName: '...' }
  4. JWT validation : Server validates JWT token and extracts rpId and externalUserId
  5. Safe address resolution : Server resolves Safe address for the authenticated user
  6. Connection upgrade : Temporary connection replaced with authenticated connection
  7. BCReader subscription : Server subscribes to BCReader WebSocket for Safe address updates
  8. Initial data sent : Server sends balance_data, transaction_data, chainid_data, recovery_data, user_data
  9. Real-time updates : Server forwards balance_update and new_transaction messages from BCReader
  10. Status updates : Server sends user.iban.updated and user.ky.updated when status changes
  11. On-demand requests : Client can request get_balance or get_transactions at any time
  12. Connection maintenance : Server sends ping every 30 seconds, client responds with pong
  13. Reconnection : On connection loss, client reconnects and re-authenticates
← Back to Main Guide