Skip to content

Get Balance Updates (WebSocket)

Retrieve historical deposit, withdrawal, and transfer transactions for a subaccount through the WebSocket connection. This endpoint returns balance changes from on-chain deposits, withdrawals, and internal transfers, excluding funding payments.

Endpoint

ws.send() wss://api.synthetix.io/v1/ws/trade

Request

Request Format

{
  "id": "balance-updates-1",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "actionFilter": "DEPOSIT",
    "limit": 50,
    "offset": 0,
    "startTime": 1704067200000,
    "endTime": 1704153600000,
    "expiresAfter": 1704153600,
    "signature": {
      "v": 28,
      "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
      "s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
    }
  }
}

Parameters

Request Parameters

ParameterTypeRequiredDescription
idstringYesClient-generated unique request identifier
methodstringYesMust be "post"
paramsobjectYesContains all parameters for the request

Params Object

ParameterTypeRequiredDescription
actionstringYesMust be "getBalanceUpdates"
subAccountIdstringYesSubaccount identifier
actionFilterstringNoFilter by action type: "DEPOSIT", "WITHDRAWAL", "TRANSFER", or comma-separated (e.g. "DEPOSIT,WITHDRAWAL"). Defaults to all types
limitintegerNoMaximum number of results to return (default: 50, max: 1000)
offsetintegerNoPagination offset (default: 0, max: 10000)
startTimeintegerNoStart of time range as Unix timestamp in milliseconds. Defaults to 7 days before endTime (or 7 days ago if neither is set)
endTimeintegerNoEnd of time range as Unix timestamp in milliseconds. Defaults to 7 days after startTime (or now if neither is set). Maximum range is 365 days
expiresAfterintegerNoOptional expiration timestamp in seconds
signatureobjectYesEIP-712 signature using SubAccountAction type
Important Notes:
  • This endpoint uses SubAccountAction EIP-712 type (no nonce required)
  • Returns deposit, withdrawal, and internal transfer transactions (excludes funding payments)
  • For funding payment history, use Get Funding Payments

EIP-712 Signature

const domain = {
  name: "Synthetix",
  version: "1",
  chainId: 1,
  verifyingContract: "0x0000000000000000000000000000000000000000",
};
 
const types = {
  SubAccountAction: [
    { name: "subAccountId", type: "uint256" },
    { name: "action", type: "string" },
    { name: "expiresAfter", type: "uint256" },
  ],
};
 
const message = {
  subAccountId: "1867542890123456789",
  action: "getBalanceUpdates",
  expiresAfter: 0,
};
 
const signature = await signer._signTypedData(domain, types, message);

Response Format

Success Response

{
  "id": "balance-updates-1",
  "status": 200,
  "result": {
    "status": "success",
    "response": {
      "balanceUpdates": [
        {
          "id": "12345",
          "subAccountId": "1867542890123456789",
          "action": "DEPOSIT",
          "status": "success",
          "amount": "1000.00",
          "fee": "0.00",
          "grossAmount": "1000.00",
          "collateral": "USDT",
          "timestamp": 1704067200000
        }
      ]
    }
  }
}

Empty Result

{
  "id": "balance-updates-1",
  "status": 200,
  "result": {
    "status": "success",
    "response": {
      "balanceUpdates": []
    }
  }
}

Error Response

{
  "id": "balance-updates-1",
  "status": 400,
  "result": null,
  "error": {
    "code": 400,
    "message": "limit cannot exceed 1000"
  }
}

Response Fields

Balance Update Object

FieldTypeDescription
idstringUnique transaction ID (string for JS BigInt compatibility)
subAccountIdstringSubaccount ID associated with the transaction (string for JS BigInt compatibility)
actionstringAction type: "DEPOSIT", "WITHDRAWAL", or "TRANSFER"
statusstringTransaction status: "pending", "success", or "failed"
amountstringNet amount changed (positive for deposits, negative for withdrawals)
feestringFee charged for the transaction
grossAmountstringTotal amount including fee (amount + fee)
collateralstringCollateral symbol (e.g., "USDT", "WETH")
timestampintegerUnix timestamp in milliseconds when the transaction was created
destinationAddressstringDestination address for withdrawals (optional, only for withdrawals)
txHashstringOn-chain transaction hash (optional)
fromSubAccountIdstringSource subaccount ID for internal transfers (optional)
toSubAccountIdstringTarget subaccount ID for internal transfers (optional)

Code Examples

Get All Balance Updates

{
  "id": "all-updates",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Get Deposits Only

{
  "id": "deposits-only",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "actionFilter": "DEPOSIT",
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Get Withdrawals Only

{
  "id": "withdrawals-only",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "actionFilter": "WITHDRAWAL",
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Paginated Request

{
  "id": "paginated",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "limit": 100,
    "offset": 100,
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Implementation Example

class BalanceUpdatesQuery {
  constructor(ws, signer) {
    this.ws = ws;
    this.signer = signer;
    this.pendingRequests = new Map();
  }
 
  async getBalanceUpdates(options = {}) {
    const {
      subAccountId,
      actionFilter = null,
      limit = 50,
      offset = 0,
      expiresAfter = 0,
    } = options;
 
    const signature = await this.signSubAccountAction(
      subAccountId,
      "getBalanceUpdates",
      expiresAfter,
    );
 
    const request = {
      id: `balance-updates-${Date.now()}`,
      method: "post",
      params: {
        action: "getBalanceUpdates",
        subAccountId,
        limit,
        offset,
        expiresAfter,
        signature,
      },
    };
 
    if (actionFilter) {
      request.params.actionFilter = actionFilter;
    }
 
    return this.sendRequest(request);
  }
 
  async signSubAccountAction(subAccountId, action, expiresAfter = 0) {
    const domain = {
      name: "Synthetix",
      version: "1",
      chainId: 1,
      verifyingContract: "0x0000000000000000000000000000000000000000",
    };
 
    const types = {
      SubAccountAction: [
        { name: "subAccountId", type: "uint256" },
        { name: "action", type: "string" },
        { name: "expiresAfter", type: "uint256" },
      ],
    };
 
    const message = {
      subAccountId: BigInt(subAccountId),
      action,
      expiresAfter: BigInt(expiresAfter),
    };
 
    const sig = await this.signer.signTypedData(domain, types, message);
    const { v, r, s } = ethers.Signature.from(sig);
    return { v, r, s };
  }
 
  sendRequest(request) {
    return new Promise((resolve, reject) => {
      this.pendingRequests.set(request.id, { resolve, reject });
      this.ws.send(JSON.stringify(request));
 
      setTimeout(() => {
        if (this.pendingRequests.has(request.id)) {
          this.pendingRequests.delete(request.id);
          reject(new Error("Request timeout"));
        }
      }, 10000);
    });
  }
}
 
// Usage
const query = new BalanceUpdatesQuery(ws, signer);
 
// Get all deposits
const deposits = await query.getBalanceUpdates({
  subAccountId: "1867542890123456789",
  actionFilter: "DEPOSIT",
});
 
console.log(`Found ${deposits.response.balanceUpdates.length} deposits`);

Implementation Notes

  • Returns deposit, withdrawal, and internal transfer transactions (excludes funding payments)
  • Results are ordered by timestamp (most recent first)
  • Default limit is 50 results per request, maximum is 1000
  • Pagination offset maximum is 10,000
  • Use pagination with limit and offset for large result sets
  • When startTime and endTime are both omitted, the default time window is the last 7 days
  • When only one time bound is supplied, the other is automatically extended by 7 days (end time is capped at now)
  • Maximum allowed time range is 365 days; requests spanning a longer period are rejected
  • Authentication requires signature by account owner or authorized delegate

Common Errors

Error CodeMessageDescription
400subAccountId is requiredNo subaccount ID was provided
400limit cannot exceed 1000Limit parameter exceeds maximum
400limit must be non-negativeNegative limit value provided
400offset must be non-negativeNegative offset value provided
400offset cannot exceed 10000Offset exceeds maximum of 10000
400invalid action filter, available filters: DEPOSIT, WITHDRAWAL, TRANSFERInvalid action filter value
Error CodeDescriptionRetryable
UNAUTHORIZEDEIP-712 signature validation failedNo
VALIDATION_ERRORRequest validation failedNo
MISSING_REQUIRED_FIELDRequired field is missingNo
INVALID_FORMATField format is invalidNo
INVALID_VALUEInvalid parameter valueNo
RATE_LIMIT_EXCEEDEDToo many requests in time windowYes
INSUFFICIENT_MARGINNot enough margin for tradeNo
ORDER_NOT_FOUNDOrder does not existNo
OPERATION_TIMEOUTOperation timed outYes

Next Steps