Skip to main content
Use this stream instead of polling /2/wallet/positions/perp/open and /2/wallet/positions/perp/unfilled. REST is still useful as a synchronous seed before the first WSS frame arrives.

Endpoint

  • URL: wss://hawk-api-prod-eu.mobula.io/
  • Channels (subscribe once per channel):
    • perp-positions-open — currently filled / open perp positions
    • perp-positions-unfilled — pending limit / stop / TP / SL orders that haven’t filled yet
Each channel is scoped to a single wallet. To track multiple wallets, open one subscription per (wallet, channel).

Subscribe

Parameters

  • event (required) — "perp-positions-open" or "perp-positions-unfilled".
  • data.wallet (required) — wallet address. Lowercased on the wire — match accordingly.
  • data.subscriptionTracking (optional, boolean) — when true, the server tags pushed frames with the subscriptionId so multiple subscriptions can be muxed on one socket. Default: false.
  • data.subscriptionId (optional) — client-generated id echoed back on every push for that subscription. Auto-generated if omitted.
  • authorization (required) — your Mobula server-side API key.
subscriptionTracking on this channel is the boolean true. The sibling Perp Events Stream takes the string "true" instead — the two channels currently use different envelope shapes.

Server push

Each frame contains the full position set for the channel — there is no diff/patch protocol. Replace your local state with data on every push.

Field reference

The data[] items mirror the REST response of GET /2/wallet/positions/perp/open (for perp-positions-open) and GET /2/wallet/positions/perp/unfilled (for perp-positions-unfilled). The unfilled channel additionally carries type: "STOP" | "LIMIT". The collateral field (numeric, the USD value of the margin actually posted on the position) is present on WSS frames; it is also documented on the REST response.

Composite id format

e.g. pos-gains-inj-usd-usdc-0xaa0055ef84ef93138c7c11be1d19dac5dcd08741-0. When closing a Gains position via /2/perp/payloads/close-position, do not pass the full composite id — extract the trailing trade-index segment and send it as a string (e.g. "0", "919").

Snapshot semantics

  • The first frame after a successful subscribe is the current snapshot of the channel. You can subscribe without a REST seed.
  • Every subsequent frame is also a full snapshot (not a diff). Replace state wholesale on receive.
  • An empty data: [] push means the channel is currently empty (no open positions / no pending orders).

Heartbeat & idle timeout

The server expects a periodic ping to keep the socket alive:
The server replies with { "event": "pong" }. A 30-second interval is safe in practice. The idle timeout is short (~10 s) before a subscription is sent — make sure your first frame after open is a subscribe, not silence.

Chain coverage

Lighter is exposed as lighter:301 on this channel. The sibling Perp Events Stream emits Lighter events under lighter:304. Subscribe with the right chain id depending on which stream you are wiring.

Implementation example

Perp Events Stream

Trade-lifecycle events (fills, liquidations, TP/SL, cancels).

Get Perp Positions

REST endpoint — useful as a synchronous seed.

Get Perp Unfilled Orders

REST endpoint for pending orders.

Execution Cookbook

Full build → execute flow.