> 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/builder-data/builderapprovedfundings.md).

# builderApprovedFundings

Stream every hourly funding payment of users who had an active fee approval for your builder at the funding block.

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

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

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 builder convenience.
{% endhint %}

### What it streams

Hyperliquid settles funding once an hour, in one block, for every open perp position. This channel delivers the funding payments of every user who held an active fee approval for your builder at that block — one entry per `(user, coin)` position — in the same `delta` shape the REST [`userFunding`](/readme/rest-api/historical-data/userfunding.md) response uses.

It is the funding counterpart of [`builderApprovedFills`](/readme/websocket/builder-data/builderapprovedfills.md) and follows the same rules:

* Attribution comes from approval state at the block, not from anything on the payment itself, and revoking an approval does not retract past payments.
* A user may hold approvals from several builders. Each subscribed builder receives its own copy.
* An approval is treated as revoked only when its max fee rate is set to exactly `0%`.

Funding settles once an hour, so expect a burst at the top of every hour rather than a steady stream: a builder with many approving users receives every one of their positions at once. That batch is split into chunks of a fixed size, delivered in `(user, coin)` order, each with its own `seq` and `cursor`.

{% hint style="info" %}
**This channel is always chunked.** Messages carry `chunk`, `totalChunks` and `batchId` on every connection, whether or not you passed `liveFormat=chunked-v1`, so a large batch never arrives as one oversized frame. Group by `batchId`, order by `chunk`, and you have the block's complete batch when you hold `totalChunks` of them. See [Live Message Chunking](/readme/websocket/live-message-chunking.md).
{% endhint %}

### Subscribe

```
{
    "type": "subscribe",
    "subscription": {
        "type": "builderApprovedFundings",
        "builder": "0xb84168cf3be63c6b8dad05ff5d755e97432ff80b"
    }
}
```

`builder` is required and must be a 20-byte hex address. It is matched case-insensitively.

### Unsubscribe

```
{
    "type": "unsubscribe",
    "subscription": {
        "type": "builderApprovedFundings",
        "builder": "0xb84168cf3be63c6b8dad05ff5d755e97432ff80b"
    }
}
```

### Funding data format

Each entry is one user's payment for one coin. `time` is the block time of the funding event in milliseconds; `usdc` is the amount paid (negative) or received (positive).

{% hint style="info" %}
**Reconnection Note:** When reconnecting with a session, replay and live events may overlap. Deduplicate using `(time, user, delta.coin)` - skip entries whose tuple you have already processed. Entries within one message are ordered by `(user, coin)`. See [Session Management](/readme/websocket/session-management-and-reconnection.md#deduplication) for details.
{% endhint %}

```json
{
  "type": "builderApprovedFundings",
  "channel": "builderApprovedFundings",
  "seq": 1,
  "cursor": "500:1704067200000:0x742d35cc6634c0532925a3b844bc9e7595f7f2e2:ETH",
  "fundings": [
    {
      "user": "0x742d35cc6634c0532925a3b844bc9e7595f7f2e2",
      "time": 1704067200000,            // block time of the funding event (ms)
      "delta": {
        "type": "funding",
        "coin": "ETH",
        "usdc": "-0.2513",              // funding paid (negative) or received (positive)
        "szi": "1.5",                   // signed position size the payment was computed on
        "fundingRate": "0.0000125"      // hourly funding rate
      }
    }
  ]
}
```

#### Cursor

The cursor of every message names the last entry it carried. Resuming from it, including from the middle of an hour's batch, delivers exactly the entries after that one, which is what makes a reconnect mid-batch lossless. Reconnecting without a cursor of your own, on the server-stored session position alone, replays the whole batch of that hour; deduplicate as described above.

### 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: 'builderApprovedFundings',
                builder: '0xb84168cf3be63c6b8dad05ff5d755e97432ff80b'
            }
        }));
    } else if (msg.type === 'ping') {
        ws.send(JSON.stringify({ type: 'pong' }));
    } else if (msg.type === 'builderApprovedFundings') {
        console.log(`Received ${msg.fundings.length} approved funding payments`);
    }
});
```

{% endtab %}

{% tab title="Python" %}

```python
import websocket
import json
import os

def on_message(ws, message):
    msg = json.loads(message)

    if msg['type'] == 'connected':
        ws.send(json.dumps({
            "type": "subscribe",
            "subscription": {
                "type": "builderApprovedFundings",
                "builder": "0xb84168cf3be63c6b8dad05ff5d755e97432ff80b"
            }
        }))
    elif msg['type'] == 'ping':
        print("Received ping, sending pong")
        ws.send(json.dumps({'type': 'pong'}))
    elif msg['type'] == 'builderApprovedFundings':
        print(f"Received {len(msg['fundings'])} approved funding payments")
    elif msg['type'] == 'subscriptionUpdate':
        print(f"Subscription update: {msg}")
    elif msg['type'] == 'error':
        print(f"Error: {msg}")
    else:
        print(f"Unknown message type: {msg}")

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

{% endtab %}
{% endtabs %}

#### Error messages:

```
{
    "type": "error",
    "message": "Builder address required for builderApprovedFundings subscription"
}
```

#### Common errors

1. ```
   Builder address required for builderApprovedFundings subscription
   ```
2. ```
   Invalid address format
   ```
3. ```
   Too many subscriptions - Maximum builder subscriptions per API key
   ```
4. ```
   Invalid API key
   ```
