> For the complete documentation index, see [llms.txt](https://docs.hydromancer.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hydromancer.xyz/readme/websocket/hip4marketevents.md).

# hip4MarketEvents

Stream HIP-4 outcome and question lifecycle events — registration, child association, description changes and settlement — filtered by venue or taxonomy.

Every live data message includes both routing identifiers with the same value:

```json
{"type": "hip4MarketEvents", "channel": "hip4MarketEvents"}
```

Other payload fields are omitted above. Existing clients may continue routing on either field.

{% hint style="info" %}
💧New endpoint - this endpoint is not a part of original Hyperliquid API and is added by us for convenience.
{% endhint %}

### What this replaces

Polling [`registeredOutcomes`](/readme/rest-api/outcomes/registeredoutcomes.md) and [`settledOutcomes`](/readme/rest-api/outcomes/settledoutcomes.md) on a timer. Short-duration recurring markets can register and settle between polls; this channel pushes each transition as the chain produces it.

It is not a streaming mirror of the node's `outcomeMeta`. This is our event log of what happened, reconstructed from the chain, not a snapshot of current state. Use the REST endpoints to bootstrap current state, then stay live here.

Venue lifecycle — a venue activating or deactivating — is **not** on this channel. Use [`outcomeVenuesOverview`](/readme/rest-api/outcomes/outcomevenuesoverview.md) for that.

### Subscribe

```
{
    "type": "subscribe",
    "subscription": {
        "type": "hip4MarketEvents",
        "venue": "out"
    }
}
```

Every filter is optional; omitting all of them streams every HIP-4 market event.

| Field         | Type    | Description                                                                                                    |
| ------------- | ------- | -------------------------------------------------------------------------------------------------------------- |
| `venue`       | string? | Permissionless venue name. Absent on validator-registered markets, which therefore never match a venue filter. |
| `category`    | string? | Taxonomy category, e.g. `"Sports"`. Present on canonical (prose) markets.                                      |
| `subCategory` | string? | Taxonomy sub-category, e.g. `"Soccer"`.                                                                        |
| `class`       | string? | `"priceBinary"` or `"priceBucket"`. Present on price markets, absent on canonical ones.                        |

Filters are matched case-insensitively and combine with AND. An event that does not carry a filtered field never matches that filter.

### Unsubscribe

```
{
    "type": "unsubscribe",
    "subscription": {
        "type": "hip4MarketEvents",
        "venue": "out"
    }
}
```

### Event format

{% hint style="info" %}
**Reconnection Note:** When reconnecting with a session, replay and live events may overlap. Deduplicate on the exact tuple `(blockNumber, txIndex, entity, outcomeId or questionId)` — match it, do not order by it. Registering a question emits the question and each of its child outcomes at the same `(blockNumber, txIndex)`, so the identity is what tells them apart; the block position alone discards the siblings, and comparing `entity` as a string reverses the order the server sends them in. To resume, send back the `cursor` verbatim rather than rebuilding one. See [Session Management](/readme/websocket/session-management-and-reconnection.md#deduplication) for details.
{% endhint %}

Only actions the chain executed are delivered; rejected ones never appear. Every event carries a common header. The remaining fields depend on `entity`.

| Field             | Type    | Description                                                                                                                               |
| ----------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `entity`          | string  | `"outcome"` or `"question"`                                                                                                               |
| `action`          | string  | `"register"`, `"associate"`, `"settle"`, `"changeDescription"` or `"registerTemplate"`. Unrecognised chain actions pass through verbatim. |
| `venue`           | string? | Venue that deployed the market; absent on validator-registered ones                                                                       |
| `deployerAddress` | string? | Deployer address; absent on validator-registered markets                                                                                  |
| `time`            | int     | Block timestamp (ms)                                                                                                                      |
| `blockNumber`     | int     | Block height                                                                                                                              |
| `txIndex`         | int     | Position within the block. Shared by a question and its child outcomes                                                                    |

```json
{
  "type": "hip4MarketEvents",
  "channel": "hip4MarketEvents",
  "seq": 1,
  "cursor": "654090213:1787992549143:4:2:7003",
  "events": [
    {
      "entity": "outcome",
      "action": "register",
      "venue": "out",
      "deployerAddress": "0x0c46eb73fae2816f219fcf11f50d6d3c59b5819e",
      "time": 1787992549143,
      "blockNumber": 654090213,
      "txIndex": 4,
      "outcomeId": 7003,
      "name": "Will it rain in Lisbon on 2026-09-30?",
      "description": "class:canonical|...",
      "category": "Weather",
      "subCategory": "Precipitation",
      "quoteToken": 0,
      "yesAssetId": "#70030",
      "noAssetId": "#70031"
    }
  ]
}
```

#### Outcome fields

`outcomeId`, `name`, `description`, `class`, `underlying`, `expiry`, `targetPrice`, `period`, `category`, `subCategory`, `sideSpecs`, `yesAssetId`, `noAssetId`, `quoteToken`, `templateId`, `deployerFeeScale`, and on settlement `settleFraction` and `settleDetails`.

The tradeable coins for an outcome are `yesAssetId` and `noAssetId`, formatted `#{outcomeId * 10}` and `#{outcomeId * 10 + 1}`.

#### Question fields

`questionId`, `name`, `description`, `class`, `underlying`, `expiry`, `period`, `priceThresholds`, `category`, `subCategory`, `quoteToken`, `templateId`, `fallbackOutcomeId`, `fallbackName`, `fallbackDescription`, `namedOutcomes`, and on settlement `settleWinningOutcomeId` and `childSettles`.

`namedOutcomes` carries the question's child outcomes and is empty on settle events:

```json
"namedOutcomes": [
  { "namedIndex": 0, "outcomeId": 7011, "name": "Under 2%", "subDescription": "..." }
]
```

`childSettles` carries the per-child resolution and is empty on register events:

```json
"childSettles": [
  { "outcomeId": 7011, "settleFraction": "1.0", "details": "..." }
]
```

### Examples

{% tabs %}
{% tab title="Javascript" %}

```javascript
const WebSocket = require('ws');

const ws = new WebSocket(`wss://api.hydromancer.xyz/ws?token=${process.env.HYDROMANCER_API_KEY}`);

ws.on('message', (data) => {
    const msg = JSON.parse(data);

    if (msg.type === 'connected') {
        ws.send(JSON.stringify({
            type: 'subscribe',
            subscription: { type: 'hip4MarketEvents', venue: 'out' }
        }));
    } else if (msg.type === 'ping') {
        ws.send(JSON.stringify({ type: 'pong' }));
    } else if (msg.type === 'hip4MarketEvents') {
        for (const event of msg.events) {
            console.log(`${event.entity} ${event.action}`, event.outcomeId ?? event.questionId);
        }
    }
});
```

{% endtab %}

{% tab title="Python" %}

```python
import json
import os
import websocket

def on_message(ws, message):
    msg = json.loads(message)
    if msg["type"] == "connected":
        ws.send(json.dumps({
            "type": "subscribe",
            "subscription": {"type": "hip4MarketEvents", "venue": "out"},
        }))
    elif msg["type"] == "ping":
        ws.send(json.dumps({"type": "pong"}))
    elif msg["type"] == "hip4MarketEvents":
        for event in msg["events"]:
            print(event["entity"], event["action"], event.get("outcomeId") or event.get("questionId"))

ws = websocket.WebSocketApp(
    f"wss://api.hydromancer.xyz/ws?token={os.environ['HYDROMANCER_API_KEY']}",
    on_message=on_message,
)
ws.run_forever()
```

{% endtab %}
{% endtabs %}
