Get Balance Updates (WebSocket)
Retrieve historical deposit and withdrawal transactions for a subaccount through the WebSocket connection. This endpoint returns balance changes from on-chain deposits and withdrawals, excluding funding payments.
Endpoint
ws.send() wss://api.synthetix.io/v1/ws/tradeRequest
Request Format
{
"id": "balance-updates-1",
"method": "post",
"params": {
"action": "getBalanceUpdates",
"subAccountId": "1867542890123456789",
"actionFilter": "DEPOSIT",
"limit": 50,
"offset": 0,
"expiresAfter": 1704153600,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
}
}Parameters
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Client-generated unique request identifier |
method | string | Yes | Must be "post" |
params | object | Yes | Contains all parameters for the request |
Params Object
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "getBalanceUpdates" |
subAccountId | string | Yes | Subaccount identifier |
actionFilter | string | No | Filter by action type: "DEPOSIT", "WITHDRAWAL", or comma-separated "DEPOSIT,WITHDRAWAL". Defaults to both |
limit | integer | No | Maximum number of results to return (default: 50, max: 1000) |
offset | integer | No | Pagination offset (default: 0) |
expiresAfter | integer | No | Optional expiration timestamp in seconds |
signature | object | Yes | EIP-712 signature using SubAccountAction type |
- This endpoint uses
SubAccountActionEIP-712 type (no nonce required) - Returns deposit and withdrawal transactions only (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": {
"balanceUpdates": [
{
"id": 12345,
"subAccountId": "1867542890123456789",
"action": "DEPOSIT",
"status": 1,
"amount": "1000.00",
"collateral": "USDT",
"timestamp": 1704067200000
},
{
"id": 12346,
"subAccountId": "1867542890123456789",
"action": "WITHDRAWAL",
"status": 1,
"amount": "-500.00",
"collateral": "USDT",
"timestamp": 1704153600000,
"destinationAddress": "0xabcdef1234567890abcdef1234567890abcdef12",
"txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
]
}
}Empty Result
{
"id": "balance-updates-1",
"status": 200,
"result": {
"balanceUpdates": []
}
}Error Response
{
"id": "balance-updates-1",
"status": 400,
"result": null,
"error": {
"code": 400,
"message": "limit cannot exceed 1000"
}
}Response Fields
Balance Update Object
| Field | Type | Description |
|---|---|---|
id | integer | Unique transaction ID |
subAccountId | string | Subaccount ID associated with the transaction |
action | string | Action type: "DEPOSIT" or "WITHDRAWAL" |
status | integer | Transaction status (1 = success) |
amount | string | Amount changed (positive for deposits, negative for withdrawals) |
collateral | string | Collateral symbol (e.g., "USDT", "WETH") |
timestamp | integer | Unix timestamp in milliseconds when the transaction was created |
destinationAddress | string | Destination address for withdrawals (optional, only for withdrawals) |
txHash | string | On-chain transaction hash (optional) |
toSubAccountId | string | Target 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.balanceUpdates.length} deposits`);Implementation Notes
- Results are ordered by timestamp (most recent first)
- Default limit is 50 results per request, maximum is 1000
- Use pagination with
limitandoffsetfor large result sets - Authentication requires signature by account owner or authorized delegate
Common Errors
| Error Code | Message | Description |
|---|---|---|
| 400 | subAccountId is required | No subaccount ID was provided |
| 400 | limit cannot exceed 1000 | Limit parameter exceeds maximum |
| 400 | limit must be non-negative | Negative limit value provided |
| 400 | offset must be non-negative | Negative offset value provided |
| 400 | actionFilter must be 'DEPOSIT', 'WITHDRAWAL', or comma-separated combination | Invalid action filter value |
| Error | Description |
|---|---|
| Invalid signature | EIP-712 signature validation failed |
| Invalid market symbol | Market symbol not recognized |
| Nonce already used | Nonce must be greater than previous value |
| Rate limit exceeded | Too many requests in time window |
| Request expired | expiresAfter timestamp has passed |
Next Steps
- Get Subaccount - View current account balances
- Get Funding Payments - Funding payment history
- Withdraw Collateral - Initiate withdrawals
- REST Alternative - REST API comparison