> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polynode.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriptions & Filters

> Subscription types, filter options, and how event matching works.

## Subscribe message

Send a JSON message after connecting to start receiving events:

```json theme={null}
{
  "action": "subscribe",
  "type": "settlements",
  "filters": {
    "wallets": ["0x1234..."],
    "tokens": ["21742633..."],
    "slugs": ["bitcoin-100k-2026"],
    "condition_ids": ["0xabc..."],
    "side": "BUY",
    "status": "pending",
    "min_size": 100,
    "max_size": 10000,
    "event_types": ["settlement", "status_update"],
    "snapshot_count": 50,
    "since": 1774412600000
  }
}
```

All fields except `action` are optional.

## Subscribe response

Successful subscriptions return an acknowledgement:

```json theme={null}
{
  "type": "subscribed",
  "subscriber_id": "connection-id",
  "subscription_id": "connection-id:0",
  "subscription_type": "combos"
}
```

Event messages use this envelope:

```json theme={null}
{
  "type": "combo_execution",
  "timestamp": 1781054110000,
  "data": {
    "event_type": "combo_execution",
    "tx_hash": "0xb0411705aaefc17991e4121acecd8aad03901ddc359cfdb7cf8773f46942927a"
  }
}
```

## Subscription types

The `type` field selects a preset of event types. If you also set `filters.event_types`, it overrides the preset.

<ResponseField name="fills" type="string">
  Events: `settlement` (reformatted as one flat event per fill)

  The simplest way to stream Polymarket trades. Each fill arrives as its own flat event with clear fields: `user` (the wallet whose fill this is), `side`, `price`, `shares_normalized`, `token_label` (outcome name), and `title` (market name). No arrays to iterate, no nested objects to unpack.

  Pre-confirmation delivery, 3-5 seconds before on-chain settlement. Same speed as `settlements`, just a cleaner format for per-trade tracking.

  ```javascript theme={null}
  const ws = new WebSocket("wss://ws.polynode.dev/ws?key=YOUR_KEY");

  ws.on("open", () => {
    ws.send(JSON.stringify({ action: "subscribe", type: "fills" }));
  });

  ws.on("message", (raw) => {
    const msg = JSON.parse(raw);
    if (msg.type !== "event") return;

    const fill = msg.data;
    console.log({
      outcome: fill.token_label,
      side: fill.side,
      price: fill.price,
      shares: fill.shares_normalized,
      user: fill.user,
      market: fill.title
    });
  });
  ```

  **Example event:**

  ```json theme={null}
  {
    "type": "event",
    "data": {
      "order_hash": "0x7b281ecba532e471...",
      "user": "0x0c6367b4e0022ca702ad6f33cd2cf504b2cc3cbb",
      "taker": "0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e",
      "tx_hash": "0xf7d09a5f24609e82...",
      "side": "BUY",
      "price": 0.59,
      "shares": 33898304,
      "shares_normalized": 33.898304,
      "token_id": "111868385514416639...",
      "token_label": "Astralis",
      "condition_id": "0xd546a1f7ff24a008...",
      "market_slug": "cs2-ast10-eye-2026-04-09-game1",
      "title": "Counter-Strike: Astralis vs EYEBALLERS - Map 1 Winner",
      "timestamp": 1775731431,
      "block_number": null,
      "log_index": null
    }
  }
  ```

  `block_number: null` means pre-confirmation (detected before the block). Once confirmed, a `status_update` fires on the `settlements` subscription if you have one active. If you only need trades and don't care about the confirmation lifecycle, `fills` is all you need.
</ResponseField>

<ResponseField name="settlements" type="default">
  Events: `settlement`, `status_update`

  Pending and confirmed Polymarket settlements. The default subscription type. Each event contains a `trades[]` array with all fills in the transaction. Use this when you need the full settlement lifecycle (pending detection, block confirmation via `status_update`, and all fills grouped by transaction).

  If you just want individual trades in a flat format, use [`fills`](#fills) instead.
</ResponseField>

<ResponseField name="trades" type="string">
  Events: `settlement`, `trade`, `status_update`

  All trade activity including confirmed on-chain trades.
</ResponseField>

<ResponseField name="prices" type="string">
  Events: `settlement`, `trade`

  Price-moving events only. Useful for building price feeds.
</ResponseField>

<ResponseField name="combos" type="string">
  Events: `combo_execution`, `combo_status_update`

  Polymarket combo executions and confirmations. Pending `combo_execution` events are decoded before on-chain confirmation; confirmed `combo_status_update` events include receipt-derived fills, transfers, fees, gas fields, and confirmation latency when the pending event was seen first.

  Combo events are enrichment-gated. Customer-facing combo executions and status updates include enriched `legs[]` metadata; events without enough leg metadata are held instead of emitted partially.

  Lifecycle and approval events are available by explicit request:

  ```json theme={null}
  {
    "action": "subscribe",
    "type": "combos",
    "filters": {
      "event_types": [
        "combo_execution",
        "combo_status_update",
        "combo_lifecycle",
        "combo_approval"
      ]
    }
  }
  ```

  See [Combo Stream](/websocket/combos) for the full field reference and filters.
</ResponseField>

<ResponseField name="dome" type="string">
  Events: `settlement` (reformatted as per-fill flat events)

  Dome API-compatible feed. Each settlement is exploded into individual flat events — one per order fill — matching the exact field names and structure of Dome's WebSocket API. Designed as a drop-in replacement for Dome with 3-5 second faster delivery. See [Dome Migration](/dome-migration) for full field reference and copy trading examples.

  This is the simplest subscription type for **per-wallet trade tracking**. Each fill arrives as its own flat event with a `user` field. Filter incoming events by `event.data.user === your_wallet` to get the wallet's actual trades — no array iteration, no maker/taker confusion.

  ```javascript theme={null}
  import WebSocket from "ws";

  const TRACKED_WALLET = "0x...";
  const ws = new WebSocket("wss://ws.polynode.dev/ws?key=YOUR_KEY");

  ws.on("open", () => {
    ws.send(JSON.stringify({
      action: "subscribe",
      type: "dome"
    }));
  });

  ws.on("message", (raw) => {
    const msg = JSON.parse(raw);
    if (msg.type !== "event") return;

    const fill = msg.data;
    if (fill.user.toLowerCase() !== TRACKED_WALLET.toLowerCase()) return;

    console.log(
      fill.side, fill.shares_normalized, fill.token_label,
      "@", fill.price, "in", fill.title
    );
  });
  ```

  **Real captured event** (live data from April 9, 2026):

  ```json theme={null}
  {
    "type": "event",
    "data": {
      "order_hash": "0x7b281ecba532e47147e9919e073635b44ce0361527f46ee1067f2ca152aea765",
      "user": "0x0c6367b4e0022ca702ad6f33cd2cf504b2cc3cbb",
      "taker": "0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e",
      "tx_hash": "0xf7d09a5f24609e8209298d7be35fa33ba8a3749ebb51f8a13d10d547467d104d",
      "side": "BUY",
      "price": 0.59,
      "shares": 33898304,
      "shares_normalized": 33.898304,
      "token_id": "111868385514416639450414573730782830447093166389406898626712932606174471659926",
      "token_label": "Astralis",
      "condition_id": "0xd546a1f7ff24a008a90bf80a06a7f7aa034abb33311d6727d325f9b848e93940",
      "market_slug": "cs2-ast10-eye-2026-04-09-game1",
      "title": "Counter-Strike: Astralis vs EYEBALLERS - Map 1 Winner",
      "timestamp": 1775731431,
      "block_number": null,
      "log_index": null
    }
  }
  ```

  In this event, `user` is the wallet whose trade this is (`0x0c6367b4...`), and `taker` is the CTF Exchange contract (`0x4bfb41d5...`) — that pattern means this is the user's own fill, not a counterparty fill. The user bought Astralis at 0.59 for 33.9 shares.

  The same transaction also produces a separate event for the counterparty (with `user = counterparty_wallet, taker = real_taker_wallet`). To track only the wallet you care about, filter by `event.data.user === your_wallet` and ignore everything else.
</ResponseField>

<ResponseField name="blocks" type="string">
  Events: `block`

  New Polygon block notifications with Polymarket-specific stats (settlement count, trade volume).
</ResponseField>

<ResponseField name="wallets" type="string">
  Events: `settlement`, `trade`, `position_change`, `deposit`, `status_update`, `position_split`, `position_merge`, `positions_converted`, `redemption`

  All activity for specific wallets. Best used with the `wallets` filter.
</ResponseField>

<ResponseField name="redemptions" type="string">
  Events: `redemption`

  Direct feed for payout redemptions. Includes normal Conditional Tokens redemptions and PM2 AutoRedeemer redemptions bridged into the same `redemption` shape.
</ResponseField>

<ResponseField name="markets" type="string">
  Events: `settlement`, `trade`, `position_change`, `status_update`, `position_split`, `position_merge`, `positions_converted`, `redemption`

  All activity for specific markets. Best used with `tokens`, `slugs`, or `condition_ids` filters.
</ResponseField>

<ResponseField name="deposits" type="string">
  Events: `deposit`

  USDC.e and Polymarket USD deposit/withdrawal activity involving known Polymarket contracts.
</ResponseField>

<ResponseField name="large_trades" type="string">
  Events: `settlement`, `trade`

  Whale alerts. Defaults to `min_size: 1000` (\$1K+) unless you set your own.
</ResponseField>

<ResponseField name="global" type="string">
  Events: `settlement`, `trade`, `position_change`, `deposit`, `block`, `status_update`, `oracle`, `redemption`

  Unfiltered stream of the default public event set (**firehose**). High throughput — use with caution. This counts toward your plan's firehose connection limit. Combo events are not included in `global` by default; use `type: "combos"` for combo activity.
</ResponseField>

<Note>
  **Firehose** refers to any WebSocket subscription with no filters applied — receiving every event at full throughput. Filtered subscriptions (specifying wallets, tokens, slugs, or size thresholds) don't count toward your firehose limit and use far less bandwidth.
</Note>

<ResponseField name="oracle" type="string">
  Events: `oracle`

  UMA Optimistic Oracle events: market resolutions, proposals, disputes, flags, and admin actions. See [Oracle Event](/websocket/events/oracle).
</ResponseField>

<ResponseField name="chainlink" type="string">
  Events: `price_feed`

  Real-time crypto prices (\~1 update/second per feed). 7 feeds available: BTC, ETH, SOL, BNB, XRP, DOGE, HYPE. Use the `feeds` filter to select specific feeds (e.g. `["BTC/USD", "ETH/USD"]`). See [Crypto Prices](/crypto/overview).
</ResponseField>

## Filters

All filters are optional. Omit the `filters` object entirely to receive all events matching the subscription type.

### Market identification

<ParamField body="tokens" type="string[]">
  Filter by Polymarket conditional token IDs. Match if **any** token in the list appears in the event.

  ```json theme={null}
  "tokens": ["21742633143463906290569404...", "98765432..."]
  ```
</ParamField>

<ParamField body="slugs" type="string[]">
  Filter by market URL slugs. Resolved to token IDs at subscribe time — zero performance cost after that.

  ```json theme={null}
  "slugs": ["bitcoin-100k-2026", "trump-2024"]
  ```

  <Warning>If a slug isn't found in metadata, you'll get a warning in the `subscribed` response. The subscription still activates for any resolved slugs.</Warning>
</ParamField>

<ParamField body="condition_ids" type="string[]">
  Filter by Polymarket condition IDs. Resolved to token IDs at subscribe time.

  ```json theme={null}
  "condition_ids": ["0x123abc..."]
  ```
</ParamField>

### Combo identification

These filters apply to `type: "combos"` subscriptions.

<ParamField body="combo_condition_ids" type="string[]">
  Filter by derived combo condition IDs.

  ```json theme={null}
  "combo_condition_ids": ["0xcombo..."]
  ```
</ParamField>

<ParamField body="leg_position_ids" type="string[]">
  Filter by constituent leg position IDs. `tokens` also matches combo and leg position IDs, but `leg_position_ids` makes the intent explicit.

  ```json theme={null}
  "leg_position_ids": ["123456789..."]
  ```
</ParamField>

<ParamField body="event_ids" type="string[]">
  Filter by combo leg event IDs when available.
</ParamField>

<ParamField body="module_ids" type="number[]">
  Filter by combo module ID. Common values are `1` for binary, `2` for negative-risk, and `3` for combinatorial legs.
</ParamField>

<ParamField body="action" type="string">
  Filter `combo_lifecycle` events by lifecycle action. Values include `Prepare`, `Split`, `Merge`, `HorizontalSplit`, `HorizontalMerge`, `Convert`, `Redeem`, `Wrap`, `Unwrap`, `Transfer`, `Compress`, `Extract`, `Inject`, `ConvertToYesBasket`, and `MergeFromYesBasket`.
</ParamField>

<ParamField body="direction" type="string">
  Filter `combo_execution` events by requester direction.

  Values: `"BUY"` or `"SELL"`
</ParamField>

<Note>
  For `type: "combos"`, `condition_ids` are matched directly against combo and leg condition IDs. They are not resolved through standard market metadata at subscription time.
</Note>

### Wallet filtering

<ParamField body="wallets" type="string[]">
  Filter by wallet addresses (case-insensitive). Matches if the wallet is involved as maker **or** taker.

  ```json theme={null}
  "wallets": ["0xabcdef1234567890..."]
  ```
</ParamField>

<Warning>
  **Tracking a specific wallet's trades?** Always match by `maker`, never by `taker`.

  For `settlement` events: iterate `data.trades[]` and find entries where `fill.maker === your_wallet`.

  For `trade` events: filter incoming events by `data.maker === your_wallet`.

  The `maker` field on each fill is the wallet that placed the order. The `taker` field is the counterparty — matching against it returns the wrong wallet's perspective with the **opposite token** and the **complement price** (e.g. shows `BUY Down 0.20` when the wallet actually bought `Up at 0.80`). This is how on-chain `OrderFilled` events are structured. See the [Settlement Event](/websocket/events/settlement#tracking-a-specific-wallets-trades) or [Trade Event](/websocket/events/trade#tracking-a-specific-wallets-trades) reference for full code examples.
</Warning>

### Trade filtering

<ParamField body="side" type="string">
  Filter by trade side. Case-insensitive.

  Values: `"BUY"` or `"SELL"`
</ParamField>

<ParamField body="min_size" type="number">
  Minimum trade size in USD. Events below this size are dropped.

  ```json theme={null}
  "min_size": 1000
  ```
</ParamField>

<ParamField body="max_size" type="number">
  Maximum trade size in USD. Events above this size are dropped.
</ParamField>

<ParamField body="status" type="string" default="all">
  Filter by settlement status.

  | Value         | Description                          |
  | ------------- | ------------------------------------ |
  | `"pending"`   | Only pre-chain detections            |
  | `"confirmed"` | Only confirmed in a block            |
  | `"all"`       | Both pending and confirmed (default) |
</ParamField>

### Event control

<ParamField body="event_types" type="string[]">
  Override the subscription type's default event list. Valid values:
  `"settlement"`, `"trade"`, `"status_update"`, `"block"`, `"position_change"`, `"deposit"`, `"position_split"`, `"position_merge"`, `"positions_converted"`, `"redemption"`, `"oracle"`, `"combo_execution"`, `"combo_status_update"`, `"combo_lifecycle"`, `"combo_approval"`
</ParamField>

<ParamField body="snapshot_count" type="number" default="20">
  Number of recent matching events to receive immediately after subscribing. Max varies by tier (free: 20, starter: 100, growth: 200, enterprise: 500).
</ParamField>

<ParamField body="since" type="number">
  UNIX timestamp in milliseconds. When set, the initial snapshot returns all events **after** this timestamp instead of just the most recent N events. Useful for gap-filling after a reconnect or cold start.

  ```json theme={null}
  "since": 1774412600000
  ```

  The server keeps a rolling window of recent events in memory. Timestamps older than your tier's lookback window are clamped to the window edge.

  | Tier       | Max lookback |
  | ---------- | ------------ |
  | Free       | 30 seconds   |
  | Starter    | 2 minutes    |
  | Growth     | 5 minutes    |
  | Enterprise | 5 minutes    |
</ParamField>

<Info>
  **`since` vs `snapshot_count`** — these are two separate snapshot modes. Use `snapshot_count` when you just want the last N events (capped by tier). Use `since` when you need every event after a specific timestamp (e.g. reconnecting after a drop). If you pass both, `since` takes priority and `snapshot_count` is ignored. See [tier limits](/guides/rate-limits#tier-limits) for the full breakdown.
</Info>

### Price feed filtering

<ParamField body="feeds" type="string[]">
  Filter by price feed names. Only applies to `chainlink` subscriptions. Available feeds: `BTC/USD`, `ETH/USD`, `SOL/USD`, `BNB/USD`, `XRP/USD`, `DOGE/USD`, `HYPE/USD`.

  ```json theme={null}
  "feeds": ["BTC/USD", "ETH/USD", "SOL/USD"]
  ```

  Omit to receive all 7 feeds. See [Crypto Prices](/crypto/overview) for details.
</ParamField>

## Matching logic

Filters use **AND** logic between different filter types and **OR** logic within each filter:

* An event must match **all** active filter categories (wallets AND tokens AND side AND size range)
* Within a category, matching **any** value is sufficient (wallet A OR wallet B)
* Empty/omitted filters match everything in that category

## Multiple subscriptions

You can send multiple `subscribe` messages on the same connection — they **stack**. Each subscribe creates an independent subscription with its own filters. Events matching *any* of your active subscriptions are delivered, deduplicated so you never receive the same event twice.

```javascript theme={null}
// Subscribe to two different wallet groups independently
ws.send(JSON.stringify({
  action: "subscribe",
  type: "wallets",
  filters: { wallets: ["0xaaa..."] }
}));

ws.send(JSON.stringify({
  action: "subscribe",
  type: "wallets",
  filters: { wallets: ["0xbbb..."] }
}));
// Both subscriptions are active — events for either wallet are delivered
```

## Subscribe response

After subscribing, you receive two messages:

**1. Snapshot** — recent events matching your filters:

```json theme={null}
{
  "type": "snapshot",
  "count": 20,
  "events": [...]
}
```

**2. Confirmation** — your subscription ID and any warnings:

```json theme={null}
{
  "type": "subscribed",
  "subscriber_id": "550e8400-e29b-41d4-a716-446655440000",
  "subscription_id": "550e8400-e29b-41d4-a716-446655440000:1",
  "subscription_type": "settlements",
  "warnings": []
}
```

Save the `subscription_id` if you need to remove a specific subscription later.

## Unsubscribe

### Remove a specific subscription

```json theme={null}
{"action": "unsubscribe", "subscription_id": "550e8400-...:1"}
```

Response:

```json theme={null}
{
  "type": "unsubscribed",
  "subscriber_id": "550e8400-...",
  "subscription_id": "550e8400-...:1"
}
```

### Remove all subscriptions

```json theme={null}
{"action": "unsubscribe"}
```

Response:

```json theme={null}
{"type": "unsubscribed", "subscriber_id": "550e8400-..."}
```

<Tip>
  To change filters on an existing subscription, unsubscribe by `subscription_id` and send a new subscribe with the updated filters.
</Tip>
