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

# How to Use preprocessedSubscribe

> Stream pre-execution Solana transactions over WebSocket with the preprocessedSubscribe method — subscribe, filter by account, and decode binary payloads.

> [!NOTE]
>
> **Public Beta.** `preprocessedSubscribe` is available on **all paid plans**
> and is metered at **0.1 credits per message** (one message per delivered
> transaction).

## What is `preprocessedSubscribe`?

`preprocessedSubscribe` is a Helius WebSocket method that streams preprocessed transactions — pre-execution Solana transactions delivered **before they reach the `processed` commitment level**. Helius aggregates multiple pre-execution sources — primarily shreds decoded directly as they arrive at the validator, supplemented by [preconfirmation](https://www.helius.dev/docs/pre-confirmations/overview.md) signals — and delivers them as a single deduplicated stream of compact binary messages, with no deshredding infrastructure on your side.

Transactions sourced from preconfirmation signals arrive later on this feed than on the dedicated [Preconfirmations](https://www.helius.dev/docs/pre-confirmations/overview.md) product, which remains the earliest access to them.

It is the successor to the earlier preprocessed LaserStream (gRPC) product. If you consume preprocessed transactions over gRPC today, switch to this method — it delivers the same class of data over a plain WebSocket connection at lower latency, and the gRPC delivery will be deprecated.

| Stream | Relative timing | Coverage | Data |
| - | - | - | - |
| [Preconfirmations](https://www.helius.dev/docs/pre-confirmations/overview.md) | Earliest | Transactions scheduled by participating validators | Transaction and execution status (Helius preconfirmations only; BAM preconfirmations report unknown) |
| `preprocessedSubscribe` | Typically after Preconfirmations, before `processed` | Broad Solana transaction coverage | Signed transaction before execution |
| [`transactionSubscribe`](https://www.helius.dev/docs/rpc/websocket/transaction-subscribe) at `processed` | After execution | Processed transactions | Transaction with execution metadata |

> [!WARNING]
>
> `preprocessedSubscribe` is a **best-effort, pre-execution signal**, not a
> commitment level. A streamed transaction can fail, be dropped, or land on a
> different fork. Reconcile against a processed or confirmed stream before
> treating it as final.

## Endpoint

`preprocessedSubscribe` is served from `wss://beta.helius-rpc.com` — the Helius Gatekeeper endpoint — rather than `mainnet.helius-rpc.com`. Authenticate with your API key as a query parameter:

```
wss://beta.helius-rpc.com/?api-key=<API_KEY>
```

Each API key is limited to **10 concurrent connections/subscriptions**.

## Subscribe

Send a JSON-RPC request with the `preprocessedSubscribe` method. `params` carries the account filters and is required — `accountInclude` and `accountRequired` must specify at least one account between them (see [Filtering](#filtering)):

```json theme={"system"}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "preprocessedSubscribe",
  "params": {
    "accountInclude": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
    "accountExclude": [],
    "accountRequired": []
  }
}
```

The server acknowledges the subscription with a JSON text frame containing the subscription ID:

```json theme={"system"}
{
  "jsonrpc": "2.0",
  "result": 1,
  "id": 1
}
```

After this acknowledgement, transaction updates arrive as **binary** WebSocket frames — see [Notification payload](#notification-payload).

## Filtering

Every subscription is scoped by the account filters in `params`. Filtering happens server-side, so you only receive the transactions you care about:

```json theme={"system"}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "preprocessedSubscribe",
  "params": {
    "accountInclude": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
    "accountExclude": ["Vote111111111111111111111111111111111111111"],
    "accountRequired": []
  }
}
```

| Filter | Match behavior |
| - | - |
| `accountInclude` | Match when the transaction references **any** of the listed accounts. |
| `accountExclude` | Drop the transaction if it references **any** of the listed accounts. |
| `accountRequired` | Match only when the transaction references **all** listed accounts. |

Filter rules:

* The three filters are combined with AND logic.
* `accountInclude` and `accountRequired` must specify **at least one account** between them — there is no unfiltered full stream.
* Accounts are base58-encoded pubkeys. Each list accepts up to **5,000** addresses.

### Address lookup table (ALT) resolution

Account filters match more than the transaction's static account keys — Helius resolves [address lookup tables](https://www.helius.dev/docs/glossary.md#address-lookup-table-alt) server-side, so `accountInclude`, `accountExclude`, and `accountRequired` also match accounts a transaction loads through an ALT. Just pass the account's pubkey; no need to maintain ALT mappings or resolve tables yourself.

## Notification payload

Notifications are delivered as **binary** WebSocket frames (not JSON). Each frame carries a single transaction in a packed byte layout:

| Bytes | Field | Type | Description |
| - | - | - | - |
| 0 | `version` | `u8` | Payload schema version. Currently `1`. |
| 1–8 | `slot` | `u64` (little-endian) | The slot the transaction was observed in. |
| 9–72 | `signature` | 64 bytes | The transaction's first signature, in binary form. |
| 73+ | `transaction` | `bytes` | The signed transaction in Solana wire format. See [Decoding the transaction](#decoding-the-transaction). |

Read the fixed 73-byte prefix in order, then decode the remaining bytes to read instructions, accounts, and address-table lookups. The signature is included in the prefix so you can identify and deduplicate a transaction without decoding the full transaction body.

Always read and check the `version` byte first. If Helius needs to update the payload format, the version will increment — branch on it so your decoder keeps working across schema changes.

### Decoding the transaction

The transaction bytes are forwarded exactly as observed on the network, in the standard wire encoding for the transaction's version. Legacy and v0 transactions use the signatures-first layout that `bincode` produces. Transaction v1 ([SIMD-0385](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md)) uses a message-first layout with signatures at the end, so `bincode` fails on v1 payloads. Use a decoder that handles every version:

* **Rust:** [`agave-transaction-view`](https://docs.rs/agave-transaction-view) parses legacy, v0, and v1 transactions in place, without an intermediate copy. This is the recommended option. [`wincode`](https://docs.rs/wincode), the bincode-compatible serializer used by current Solana SDKs, also decodes v1 into `VersionedTransaction`.
* **JavaScript / TypeScript:** make sure your library version supports transaction v1. Older `VersionedTransaction.deserialize` implementations only handle legacy and v0. Use `@solana/kit` 8.0+ or `@solana/web3.js` v3. See [Transaction v1 support](https://www.helius.dev/docs/rpc/transaction-v1.md).

```rust theme={"system"}
use agave_transaction_view::transaction_view::TransactionView;

// `frame` is the full binary WebSocket message
let tx_bytes = &frame[73..];
let tx = TransactionView::try_new_unsanitized(tx_bytes)?;

println!("version: {:?}", tx.version()); // Legacy, V0, or V1
for ix in tx.instructions_iter() {
    println!("program index {}: {} bytes", ix.program_id_index, ix.data.len());
}
```

## Example

```javascript theme={"system"}
const WebSocket = require('ws');
const bs58module = require('bs58');
const bs58 = bs58module.default ?? bs58module;

const ws = new WebSocket('wss://beta.helius-rpc.com/?api-key=<API_KEY>');

ws.on('open', () => {
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'preprocessedSubscribe',
    // Only Jupiter v6 transactions — accountInclude/accountRequired must
    // specify at least one account between them.
    params: {
      accountInclude: ['JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4'],
      accountExclude: [],
      accountRequired: []
    }
  }));

  // Keep the connection alive
  setInterval(() => ws.ping(), 30_000);
});

ws.on('message', (data, isBinary) => {
  // The subscribe acknowledgement arrives as a JSON text frame
  if (!isBinary) {
    const msg = JSON.parse(data.toString());
    if (msg.id === 1) console.log('Subscribed, ID:', msg.result);
    return;
  }

  // Notifications arrive as binary frames:
  // version (u8) | slot (u64 LE) | signature ([u8; 64]) | transaction bytes
  const buf = Buffer.from(data);
  const version = buf.readUInt8(0); // currently 1 — branch on this if it changes
  if (version !== 1) return; // unknown schema version; update your decoder
  const slot = buf.readBigUInt64LE(1);
  const signature = bs58.encode(buf.subarray(9, 73));
  const txBytes = buf.subarray(73); // transaction in Solana wire format (legacy, v0, or v1)

  console.log('Preprocessed transaction:', { slot, signature, bytes: txBytes.length });
  // Decode txBytes with a decoder that supports transaction v1 (see "Decoding the transaction")
});

ws.on('error', console.error);
ws.on('close', () => process.exit(1));
```

## What data is available?

Each notification carries the signed transaction, its first signature, and its slot. Because delivery happens before execution, the stream does **not** include:

* Execution status or errors
* Pre/post balances or token balance changes
* Log messages or inner instructions
* Compute units consumed

Think of it as receiving the "proposal" without the "result" — you see what the sender tried to do, but not what actually happened. Account and program state updates don't exist yet at this stage either; if you need real-time account state, use [LaserStream gRPC](https://www.helius.dev/docs/laserstream.md) at `processed` commitment.

## Backpressure

The stream does not buffer indefinitely for slow consumers. If your client reads too slowly and more than **4,000 messages** back up server-side, Helius closes the connection — you receive a clean WebSocket close frame. Drain frames faster than they arrive: keep heavy work such as transaction decoding and strategy logic off the receive loop, and reconnect and resubscribe after a disconnect.

## Delivery guarantees

Delivery is best-effort, not guaranteed, and there is no historical replay. Clients should:

1. Reconnect and resubscribe after a connection closes.
2. Deduplicate by transaction signature.
3. Treat the slot as an observation, not finality.
4. Reconcile against a processed or confirmed stream when execution results matter.

## Pricing

`preprocessedSubscribe` is available on **all paid plans** and metered at **0.1 credits per message** — one message per delivered transaction, billed from your plan. See [Credits](https://www.helius.dev/docs/billing/credits.md) for details.

## Related

> [card] [Preconfirmations](https://www.helius.dev/docs/pre-confirmations/overview.md)
>
> Transactions streamed before they become shreds — the earliest transaction signal.

> [card] [Raw Shreds (UDP)](https://www.helius.dev/docs/shred-delivery/raw-shreds.md)
>
> Unprocessed shred packets over UDP. You implement the deshredding.

> [card] [transactionSubscribe](https://www.helius.dev/docs/rpc/websocket/transaction-subscribe.md)
>
> Post-execution transactions with rich filtering and execution metadata.


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