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

# Realtime

> Subscribe to a thread and receive every event as it happens, including the ones webhooks do not carry.

Patchwork publishes run events to a private channel per thread over Pusher. We own the Pusher account — your client fetches public config from us and asks us to sign its subscription.

This is the only transport that carries every [event type](/guides/events), including the streaming deltas and render blocks that no other transport can use. Everything else, including the pause events that tell you a run needs a human, is also deliverable by [webhook](/guides/events#registering-an-endpoint).

## Connecting

<Steps>
  <Step title="Fetch config">
    `GET /v1/loom/realtime/config` returns `{ enabled, key, cluster }`. If `enabled` is `false`, realtime is not configured for this workspace — fall back to polling.
  </Step>

  <Step title="Subscribe">
    The channel is `private-thread-` plus the thread UUID.
  </Step>

  <Step title="Authorize">
    Your client's subscription request goes to `POST /v1/loom/realtime/auth` with `channel_name` and `socket_id`. We check the caller owns that thread and return the Pusher auth payload.
  </Step>
</Steps>

A subject can only subscribe to its own threads. Asking to sign a channel for someone else's thread returns `403`.

## The React SDK does this for you

[`@usepatchwork/react`](https://www.npmjs.com/package/@usepatchwork/react) fetches config, subscribes, authorizes, and falls back to polling when realtime is disabled. If your agent lives in a React app, start there rather than wiring Pusher by hand.

## Falling back

Realtime is an optimisation, not a requirement. Everything it tells you is also discoverable:

| Instead of | Poll |
| - | - |
| `run.completed` / `run.failed` | `GET /v1/loom/runs/:id` until `terminal` |
| `message.completed` | the same call — the message is on the run |
| `write.confirmation_requested` | `GET /v1/loom/threads` → `pending_confirmation` |
| `action.proposed` | `GET /v1/loom/threads` → `pending_action` |
| `elicitation.requested` | `GET /v1/loom/threads` → `pending_elicitation` |

The one thing polling cannot reproduce is `message.delta` — streaming fragments only exist on the wire.

## Choosing a transport

* **A chat UI** — realtime. Streaming is most of the experience.
* **A backend integration** — [webhooks](/guides/events#verifying-a-delivery) for terminal events, polling for pauses.
* **A batch job** — polling. There is no session to stream to.


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