# WebSocket 訂閱

> Source: https://docs.blockvectra.com/zh-hant/guides/websocket-subscriptions/

BlockVectra 提供安全的 WebSocket 連線（`wss://`），可在標準 JSON-RPC 請求之外串流即時以太坊事件訂閱。

## 選擇 WebSocket、Webhook 或輪詢

當你的應用程式能維持連線時，請使用 WebSocket 接收即時 `newHeads` 與篩選後的 `logs`。若要在 HTTPS 端點接收關注錢包的活動，請使用[區塊鏈 Webhook API](https://docs.blockvectra.com/zh-hant/guides/webhook-push/)，並具備[原始主體簽章驗證](https://docs.blockvectra.com/zh-hant/guides/webhook-push/#verify-signatures)、重試與保留相符事件的重放。若要用於排程的 ERC-20 收款監控與歷史日誌回填，請使用 [HTTP 輪詢](https://docs.blockvectra.com/zh-hant/guides/stablecoin-payments/)。穩定幣指南也示範了 [USDT / USDC Webhook 接收端](https://docs.blockvectra.com/zh-hant/guides/stablecoin-payments/#receive-payments-with-webhooks)。若要從鏈支援、接收端需求與復原取捨等架構面比較各種方式（供開發者與 AI Agent 參考），請參閱[選擇 Webhook、WebSocket 或 RPC 輪詢指南](https://docs.blockvectra.com/zh-hant/guides/webhook-vs-websocket/)。

WebSocket 支援來自 `GET /v1/chains` 中的 `ws` 與 `subscriptions`；Push 支援來自需認證的 `GET /v1/push/chains` 清單。沒有 WebSocket 的鏈，只要列於該清單，仍可使用地址 Webhook。

WebSocket 斷線後需要重新訂閱與回填；它不會發出 Push 控制事件 `subscription.gap` 或 `chain.reorg`。就 Webhook 而言，缺口需要範圍掃描；重組通知則需要先標記或捨棄被取代的事件，再保留自動重新遞送的規範事件。[Push 重放](https://docs.blockvectra.com/zh-hant/guides/webhook-push/#delivery-retries-and-replay)會重新遞送保留的相符事件，而非地址或鏈加入之前，或訂閱離線期間的資料。實作復原時，請參閱[計費規則](https://docs.blockvectra.com/zh-hant/guides/billing-rules/)與[錯誤參考](https://docs.blockvectra.com/zh-hant/errors/)。

## 可用的鏈

你可以讀取 `GET /v1/chains` 中的 `ws`（布林值）與 `subscriptions`（支援型別的陣列），確認某個網路是否啟用 WebSocket 訂閱。

下表列出已啟用 WebSocket 支援的網路：

| 鏈 | WebSocket 端點（路徑攜帶 Key） |
| --- | --- |
| Robinhood Chain | `wss://api.blockvectra.com/v1/robinhood_mainnet/{api_key}` |
| Robinhood Chain Testnet | `wss://api.blockvectra.com/v1/robinhood_testnet/{api_key}` |

## 連線與認證

用戶端建立安全的 TLS WebSocket 連線（`wss://`）。API key 可以兩種方式提供：

* **路徑 key**：`wss://api.blockvectra.com/v1/{chain}/{api_key}`
* **標頭 key**：`wss://api.blockvectra.com/v1/{chain}`，在 HTTP Upgrade 握手期間帶上 `x-api-key: {api_key}` 或 `Authorization: Bearer {api_key}` 標頭。

使用路徑 key 時，會採用路徑 key，並忽略兩個認證標頭。沒有路徑 key 時，非空的 `x-api-key` 優先於 `Authorization: Bearer`。瀏覽器 WebSocket API 無法設定這些標頭；請使用路徑 key URL。

### 握手准入檢查

握手可能以下列原因失敗：

* **認證**：缺少 API key 會回傳 HTTP 401（[`missing_api_key`](https://docs.blockvectra.com/zh-hant/errors/#missing_api_key)）；未知、已停用或已撤銷的 API key 會回傳 HTTP 401（[`invalid_api_key`](https://docs.blockvectra.com/zh-hant/errors/#invalid_api_key)）；若認證暫時無法使用，回應為 HTTP 503（[`auth_unavailable`](https://docs.blockvectra.com/zh-hant/errors/#auth_unavailable)）。
* **帳戶餘額**：預付餘額為零或負數的帳戶會回傳 HTTP 402（[`balance_exhausted`](https://docs.blockvectra.com/zh-hant/errors/#balance_exhausted)）；若無法確認計費狀態，回應為 HTTP 503（[`billing_unavailable`](https://docs.blockvectra.com/zh-hant/errors/#billing_unavailable)）。
* **連線限制**：超過單 key 限制（20 條連線）或單帳戶限制（50 條連線）會回傳 HTTP 429（[`ws_connection_limit`](https://docs.blockvectra.com/zh-hant/errors/#ws_connection_limit)）。
* **鏈可用性**：請求未知或未提供服務的鏈會回傳 HTTP 404（[`unknown_chain`](https://docs.blockvectra.com/zh-hant/errors/#unknown_chain)）。
* **伺服器容量**：當伺服器忙碌或過載時，握手會回傳 HTTP 503（[`overloaded`](https://docs.blockvectra.com/zh-hant/errors/#overloaded)）並附帶 `Retry-After` 標頭。

連線後，用戶端可以傳送標準 JSON-RPC 2.0 請求（例如 `eth_blockNumber` 或 `eth_call`），以及以 UTF-8 文字訊框格式化的訂閱控制方法。

## 計費規則

* 建立連線、保持閒置連線開啟，以及 ping/pong 心跳不計費。
* 成功的 `eth_subscribe` 與 `eth_unsubscribe` 呼叫會計費，包括回傳 `false` 的退訂；失敗的呼叫不計費。一般 JSON-RPC 呼叫依 [JSON-RPC 計費規則](https://docs.blockvectra.com/zh-hant/guides/billing-rules/)處理。
* `newHeads` 通知以每條連線、每個區塊雜湊計一次，與該連線擁有多少個 `newHeads` 訂閱無關。
* `logs` 通知以每個訂閱、每個有相符日誌的區塊雜湊與階段計一次；沒有相符項的區塊不計費。同一區塊與階段中的多筆相符日誌不會增加費用。不同訂閱分別計費，即使其篩選條件重疊。重組日誌（`removed: true`）另計一個單位；同一高度的替代區塊具有不同雜湊，屬於不同單位。
* 通知僅在成功寫入 socket 傳送緩衝區後才計費；已排入佇列或遭丟棄且未寫出的通知不計費。在 `eth_unsubscribe` 應答前已排入佇列的通知，若成功寫出仍會計費。WebSocket 訊息不攜帶 HTTP 計費標頭；請查詢帳戶用量以取得已計量的 CU。

## 訂閱方法

此 API 實作標準的以太坊發布/訂閱介面：`eth_subscribe` 與 `eth_unsubscribe`。

### `newHeads`

每當新區塊附加到鏈頭時，發出新的區塊頭物件。

* **訂閱請求**：
  ```json
  {"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}
  ```
* **訂閱回應**：回傳不透明的十六進位訂閱識別碼：
  ```json
  {"jsonrpc":"2.0","id":1,"result":"0x1"}
  ```
* **推送通知訊框**：
  ```json
  {"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1","result":{"number":"0x1b4","hash":"0x..."}}}
  ```

### `logs`

發出符合指定篩選條件的日誌事件。

* **篩選條件要求**：每個 `logs` 訂閱篩選器**必須**指定 `address`（合約地址或地址陣列）或 `topic0`（第一個主題位置，非 null）。兩者皆未指定的篩選器（例如 `{}` 或 `{"topics":[null,"0x..."]}`）會以錯誤碼 `-32602`（[`logs_filter_required`](https://docs.blockvectra.com/zh-hant/errors/#logs_filter_required)）拒絕。

* **篩選限制**：最多 100 個地址；最多 4 個主題位置，且每個位置最多 16 個候選雜湊。

* **篩選容量**：若作用中的日誌篩選器達到容量上限，訂閱會回傳錯誤碼 `-32022`（[`ws_filter_capacity`](https://docs.blockvectra.com/zh-hant/errors/#ws_filter_capacity)）。

* **鏈重組**：若某個區塊因鏈重組而被移除，被移除日誌的通知會帶有 `"removed": true`。

* **訂閱請求**：
  ```json
  {"jsonrpc":"2.0","id":2,"method":"eth_subscribe","params":["logs",{"address":"0x1234567890123456789012345678901234567890"}]}
  ```

### `eth_unsubscribe`

使用訂閱識別碼終止作用中的訂閱。

* **退訂請求**：
  ```json
  {"jsonrpc":"2.0","id":3,"method":"eth_unsubscribe","params":["0x1"]}
  ```
* **退訂回應**：
  ```json
  {"jsonrpc":"2.0","id":3,"result":true}
  ```

## 可執行範例

**viem v2 (TypeScript)**

使用 [viem](https://viem.sh) v2，透過 `createPublicClient` 與 `webSocket` 傳輸層連線。請將 `{chain}` 替換為目標鏈識別碼，並將 `{api_key}` 替換為你的 API key：

```ts
import { createPublicClient, webSocket } from 'viem';

const apiKey = process.env.BLOCKVECTRA_API_KEY || '{api_key}';
const url = `wss://api.blockvectra.com/v1/robinhood_mainnet/${apiKey}`;

const client = createPublicClient({
  transport: webSocket(url),
});

// 1. 訂閱新的區塊頭（newHeads）
const unwatchBlocks = client.watchBlocks({
  onBlock: (block) => {
    console.log('New block header received:', block.number, block.hash);
  },
  onError: (error) => {
    console.error('watchBlocks error:', error);
  },
});

// 2. 訂閱合約事件日誌（篩選條件需要 address 或 topic0）
const unwatchEvents = client.watchEvent({
  address: '0x1234567890123456789012345678901234567890',
  onLogs: (logs) => {
    console.log('Matching logs received:', logs);
  },
  onError: (error) => {
    console.error('watchEvent error:', error);
  },
});
```


  **Command line (websocat / wscat)**

使用 `websocat` 或 `wscat` 等命令列工具連線，並傳送原始 JSON-RPC 訊框：

```bash
# 使用 websocat 以路徑 key 連線
websocat "wss://api.blockvectra.com/v1/{chain}/{api_key}"

# 或透過請求標頭傳入 key
websocat -H="x-api-key: {api_key}" "wss://api.blockvectra.com/v1/{chain}"

# 或使用 wscat 連線
wscat -c "wss://api.blockvectra.com/v1/{chain}/{api_key}"
```

在互動工作階段中傳送訂閱命令：

```json
{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}
{"jsonrpc":"2.0","id":2,"method":"eth_subscribe","params":["logs",{"address":"0x1234567890123456789012345678901234567890"}]}
{"jsonrpc":"2.0","id":3,"method":"eth_unsubscribe","params":["0x1"]}
```


## 關閉碼與用戶端動作

當伺服器終止 WebSocket 工作階段時，會傳送帶有特定關閉碼與簡短原因的 Close 訊框。下表列出伺服器發出的關閉碼與建議動作：

|                           關閉碼 | 原因字串                             | 說明                                                             | 可重試 | 用戶端動作                                                                                   |
| ----------------------------: | -------------------------------- | -------------------------------------------------------------- | :-: | --------------------------------------------------------------------------------------- |
| [1001](https://docs.blockvectra.com/zh-hant/errors/#1001) | `idle`                           | 閒置連線，3600 秒（1 小時）內沒有訂閱或訊息                                      |  是  | 視需要重新連線。                                                                                |
| [1003](https://docs.blockvectra.com/zh-hant/errors/#1003) | `binary frames are not accepted` | 收到二進位 WebSocket 訊框；僅接受 UTF-8 文字訊框                              |  否  | 不要自動重新連線。更新用戶端以傳送文字訊框。                                                                  |
| [1009](https://docs.blockvectra.com/zh-hant/errors/#1009) | `message too large`              | 入站承載超過 1 MiB                                                   |  否  | 不要自動重新連線。拆分大型請求或縮小承載大小。                                                                 |
| [1012](https://docs.blockvectra.com/zh-hant/errors/#1012) | `service restart`                | 伺服器正在重新啟動，或工作階段已達最大存續時間（24 小時）                                 |  是  | 使用隨機抖動退避重新連線、重新建立訂閱，並回填遺漏的資料。                                                           |
| [1013](https://docs.blockvectra.com/zh-hant/errors/#1013) | `chain unavailable`              | 鏈無法使用                                                          |  是  | 使用全抖動指數退避重新連線、重新建立訂閱，並回填遺漏的資料。                                                          |
| [1013](https://docs.blockvectra.com/zh-hant/errors/#1013) | `overloaded`                     | 伺服器暫時過載                                                        |  是  | 使用全抖動指數退避重新連線、重新建立訂閱，並回填遺漏的資料。                                                          |
| [4402](https://docs.blockvectra.com/zh-hant/errors/#4402) | `insufficient balance`           | 帳戶餘額已用盡                                                        |  否  | 不要自動重新連線。[儲值餘額後再重新連線](https://docs.blockvectra.com/zh-hant/guides/billing-rules/)。                                  |
| [4404](https://docs.blockvectra.com/zh-hant/errors/#4404) | `invalid api key`                | API key 未知、已停用或已撤銷                                             |  否  | 不要自動重新連線。請先在控制台確認或輪替 API key，再重新連線。                                                     |
| [4408](https://docs.blockvectra.com/zh-hant/errors/#4408) | `slow consumer`                  | 伺服器的推送佇列超過 512 KiB 時，會關閉工作階段並捨棄待處理的通知；用戶端可能收不到關閉訊框（瀏覽器回報 1006） |  是  | 將非預期的斷線（未收到關閉訊框，瀏覽器回報 1006）視同 4408：使用退避重新連線、重新建立訂閱，並以 `eth_getLogs` 回填被捨棄的資料；減少訂閱或加快讀取。 |
| [4429](https://docs.blockvectra.com/zh-hant/errors/#4429) | `push rate exceeded`             | 通知速率超過每秒 1,000 次推送                                             |  是  | 減少訂閱或縮小篩選範圍；使用退避重新連線、重新訂閱並回填。                                                           |
| [4503](https://docs.blockvectra.com/zh-hant/errors/#4503) | `billing unavailable`            | 計費暫時無法使用                                                       |  是  | 暫時狀態；使用全抖動指數退避重新連線。                                                                     |

## 重新連線與指數退避

為避免連線中斷時發生同步重新連線風暴，用戶端必須實作全抖動的指數退避：

* **退避公式**：在第 n 次重新連線嘗試之前（n = 0, 1, 2, ...），等待一段均勻隨機選取的時長：
  ```
  delay = random(0, min(20s, 0.5s * 2^n))
  ```
* **重設計數器**：只有在維持不中斷的穩定連線至少 `60 seconds` 後，才將重試計數器 n 重設為 0。
* **關閉碼 1012**：在第一次重新連線嘗試前加入隨機初始延遲，以避免同步重新連線尖峰。
* **不可重試的關閉碼**：遇到 [4402](https://docs.blockvectra.com/zh-hant/errors/#4402)、[4404](https://docs.blockvectra.com/zh-hant/errors/#4404)、[1003](https://docs.blockvectra.com/zh-hant/errors/#1003) 或 [1009](https://docs.blockvectra.com/zh-hant/errors/#1009) 時，請勿自動重新連線。

### 重新連線後回填遺漏的資料

WebSocket 訂閱不會跨連線持續存在；斷線期間發出的通知不會保留在伺服器上。重新連線後，用戶端應執行追趕策略：

1. **使用 `eth_getLogs` 回填日誌**：
   * 持久化記錄已成功處理的最高區塊編號（`last_processed_block`）。
   * 重新連線後立即呼叫 `eth_subscribe("logs", ...)` 以擷取即時事件。
   * 透過 `eth_getLogs` 查詢遺漏的區塊，設定 `fromBlock: last_processed_block + 1` 與 `toBlock: "latest"`（或即時串流收到的第一個區塊）。
   * 若斷線缺口超過該網路的 `max_logs_block_range`（來自 `GET /v1/chains`），請將查詢切分為不超過該限制的區塊。
   * 使用唯一元組 `(blockHash, transactionHash, logIndex)` 在查詢邊界上去重日誌項目。
2. **使用 `eth_getBlockByNumber` 回填區塊頭**：
   * 記錄斷線前收到的最新區塊編號與雜湊。
   * 重新訂閱 `newHeads`。
   * 查詢 `eth_getBlockByNumber("latest", false)`，並依序取得遺漏的中間區塊。驗證 `parentHash` 的鏈結連續性，以偵測重組。

## 限制

| 限制                              | 值                                           | 超過時的結果                                                                   |
| ------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------ |
| 每條 WebSocket 連線的訂閱數             | 100                                         | `-32022` [`subscription_limit`](https://docs.blockvectra.com/zh-hant/errors/#subscription_limit)     |
| 每條 WebSocket 連線的 `newHeads` 訂閱數 | 4                                           | `-32022` [`subscription_limit`](https://docs.blockvectra.com/zh-hant/errors/#subscription_limit)     |
| `logs` 訂閱篩選條件要求                 | 必須指定 `address` 或 `topic0`（`topics` 中的第一個位置） | `-32602` [`logs_filter_required`](https://docs.blockvectra.com/zh-hant/errors/#logs_filter_required) |

## 下一步

* [瀏覽資料集目錄](https://blockvectra.com/zh-hant/data/)，查看 BlockVectra 索引的每個資料集。
* [查看免費方案與定價](https://blockvectra.com/zh-hant/pricing/#free)，確認你的帳戶包含哪些內容。
* [登入控制台](https://console.blockvectra.com/login/?next=%2Fkeys%2F)建立 API key。
