Skip to main content
The userFills channel delivers every fill where your account is one of the counterparties — both fills where you were the taker (your order crossed the book and aggressed) and fills where you were the maker (your resting order was hit). To determine your role in each fill, compare the aggressor order ID (aid) and the resting order ID (rid) against your own order IDs. For the full public trade stream regardless of account, use the trades channel instead. Pass the account’s wallet address (not the numeric account ID) as the user parameter.

Subscribing

Push message format

The data array may contain multiple fill objects when several fills execute in the same block. Process them in array order.

Fill object fields

number
Contract ID on which the fill occurred.
string
Taker (aggressor) order ID — the order that crossed the book and matched against the resting order.
string
Maker (resting) order ID — the order that was already in the book and was hit by the aggressor.
string
Direction of the aggressor: "B" means the buyer was the aggressor (bought into resting asks); "S" means the seller was the aggressor (sold into resting bids).
string
Fill execution price (raw string). Apply the contract’s decimal precision before displaying to users.
string
Fill size (raw string) — the quantity that changed hands at price p.
string
Execution ID — globally unique on-chain identifier for this fill. Use this to deduplicate fills if you receive the same event from multiple sources.
string
Your signed position size before this fill settles: positive for long, negative for short, 0 if flat. The value is already picked for your side based on whether you were the taker (aid) or maker (rid) — you don’t derive it. Compute your post-fill position incrementally as startPosition ± size; when one resting order fills in several pieces, carry it forward (fill N’s sp equals the position computed from fill N−1). Position data is private, so this field appears only on the per-address userFills channel — never on the public trades channel.
number
Which of your position buckets this fill lands in: 0 = ONE_WAY, 1 = LONG, 2 = SHORT. In HEDGE mode a contract has separate long and short positions, so sp must be accumulated per ps bucket. In ONE_WAY mode ps is always 0 and can be ignored.

Determining your role

If aid matches one of your order IDs, your order was the aggressor. You paid the taker fee and your order actively crossed the spread to execute.

Envelope fields

string
"TradeFill" for a live incremental frame, "TradeFillHistorySnapshot" for the history snapshot sent on subscribe.
string
"fills.<user_address>" — the address you subscribed with.
number
Server send timestamp in Unix milliseconds. Use this as the authoritative event time for all fills in the data array.

History snapshot on subscribe

Immediately after a successful subscription the server pushes one frame carrying up to the last 10 fills, so a blotter can render history without a separate REST call. It differs from a live frame in two ways:
rows[] uses different field names from the incremental fills — you cannot reuse one parser for both. A private-channel snapshot passes through the corresponding REST response body verbatim, so it uses the full REST column names (exec_id, price, qty) while increments use the compact wire names (e, p, s). Reuse your REST parser for the snapshot.
The snapshot also carries more than an increment: notional, fee, realized_pnl, side_role, and liquidate_type appear only there.

Snapshot row fields

Working with the snapshot:
  • The payload is at data.rows, and data is an object rather than an array. (openOrders also sends an object, but its payload key is orders.)
  • side is already your own direction — you do not need to derive it from taker/maker. The incremental b is the aggressor’s direction, which is the opposite of yours when you were the maker. Do not run both through the same logic.
  • side_role states your role directly, so there is no need to compare aid against rid the way you do for increments.
  • fee, realized_pnl, and position_before are your side’s values, not totals for the whole match.
  • Quoted fields are big integers carried as strings and can exceed the JavaScript safe-integer range — never pass them through Number() before comparing or echoing them back.
  • Fewer than 10 rows, or none at all, is normal — the account may have little history, or the history service may be briefly unavailable. Do not treat it as an error.
  • The snapshot and the increments that follow may overlap. Deduplicate by execution ID (exec_id in the snapshot, e in increments); nothing is dropped.
  • The snapshot is fixed at 10 rows, with no paging and no cursor. For deeper history use userFills.
Combine userFills with orderUpdates to build a complete real-time trade blotter: orderUpdates tells you when order status changes, and userFills gives you the precise fill price and size for each execution.