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

# Subscriptions

> Use Subscribe to consume block write events and build real-time incremental pipelines.

# Subscriptions

When a new block is written to a Block table, BlockDB publishes a block subscription event through Kafka. The SDK's `Subscribe` consumes that event stream, putting your downstream business logic on the chain's "heartbeat" and closing the loop of "production → event → consumption".

## Basic usage

Subscribe to one Block table and consume block by block:

```python theme={null}
from blockdb import BlockTable
from blockdb.block_table import Subscribe

t = BlockTable("token.token.eth")

for table_id, block in Subscribe(tables=[t]).listen():
    print(table_id, block.height, block.id)
    # use block to read that block's data and trigger downstream compute
```

`listen()` is a long-lived generator that yields `(table_id, block)` on each event. `block` is a `Block` data unit (`id` / `height` / `timestamp`).

## Subscribing to multiple tables

When subscribing to several tables, events are distributed per table and `table_id` tells you the source. Passing the same table more than once is deduplicated:

```python theme={null}
a = BlockTable("token.token_transfer.eth")
b = BlockTable("address.addresses.eth")

for table_id, block in Subscribe(tables=[a, b]).listen():
    print(table_id, block.height)
```

## Specifying a start cursor

By default the subscription starts from the latest events. Use `start_at` to start consuming from a given point in time:

```python theme={null}
sub = Subscribe(tables=[t], start_at="2026-08-17T00:00:00Z")
```

`start_at` accepts an RFC3339 time string (for example `2026-08-17T00:00:00Z`) or epoch seconds (int).

<Note>
  `start_at` can look back at most **1 hour** from the current time (the server's `subscribe.max_lookback`, default `1h`). Anything earlier is rejected by the server with `InvalidArgument`.
</Note>

## Reconnection and cursor resumption

`Subscribe` has built-in reconnection, so you do not need retry logic in your business code:

* **Automatic reconnect**: whenever the stream ends (server idle timeout, restart, rebalance, or an error), it reconnects with exponential backoff (1s → 30s).
* **Cursor resumption**: reconnects carry the timestamp cursor of the last event, and the server replays the events from the disconnected window.
* **Replay window**: the server's replay window for old cursors is 1 hour; the SDK clamps the cursor to within 30 minutes, and cursors older than that are dropped in favor of subscribing from the latest (missed blocks are backfilled by height upstream).

Just consume with `for ... in listen():` — no `try/except` retry loop needed.

## Stopping a subscription

`close()` can be called from another thread to stop a `listen()` that is blocked:

```python theme={null}
sub = Subscribe(tables=[t])

# consuming on the main thread
for table_id, block in sub.listen():
    ...

# another thread wants to stop the subscription
sub.close()
```

<Info>
  If a multi-table subscription needs height alignment (so all dependency tables are ready at the target height), use `Aligned` — see [Multi-table block height alignment](/guides/multi-table-alignment).
</Info>

## Related topics

* [Writes and event subscription](/concepts/write-subscribe)
* [Multi-table block height alignment](/guides/multi-table-alignment)
* [High-concurrency compute with BlockX](/guides/blockx)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.