# WebSocket 구독

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

BlockVectra는 표준 JSON-RPC 요청과 함께 실시간 이더리움 이벤트 구독을 스트리밍할 수 있는 안전한 WebSocket 연결(`wss://`)을 제공합니다.

## WebSocket, Webhook 또는 폴링 선택

애플리케이션이 연결을 지속적으로 유지할 수 있는 경우 실시간 `newHeads` 및 필터링된 `logs`에 WebSocket을 사용하세요. 감시 대상 지갑 활동을 HTTPS 엔드포인트로 수신하려면 [블록체인 Webhook API](https://docs.blockvectra.com/en/guides/webhook-push/)를 사용하세요. [원본 본문 서명 검증](https://docs.blockvectra.com/en/guides/webhook-push/#verify-signatures), 재시도 및 보존된 일치 항목의 리플레이를 지원합니다. 스케줄링된 ERC-20 결제 모니터링 및 과거 로그 백필에는 [HTTP 폴링](https://docs.blockvectra.com/en/guides/stablecoin-payments/)을 사용하세요. 스테이블코인 가이드에서는 [USDT / USDC Webhook 수신 엔드포인트](https://docs.blockvectra.com/en/guides/stablecoin-payments/#receive-payments-with-webhooks)도 안내합니다. 개발자와 AI 에이전트를 위한 체인 지원, 수신 측 요구사항, 복구 트레이드오프 전반의 아키텍처 비교는 [Webhook, WebSocket 또는 RPC 폴링 선택 가이드](https://docs.blockvectra.com/en/guides/webhook-vs-websocket/)를 참조하세요.

WebSocket 지원 여부는 `GET /v1/chains`의 `ws` 및 `subscriptions`에서 확인할 수 있습니다. Push 지원 여부는 인증된 `GET /v1/push/chains` 목록에서 가져옵니다. WebSocket이 지원되지 않는 체인이라도 해당 목록에 포함되어 있다면 주소 Webhook을 계속 사용할 수 있습니다.

WebSocket 연결 해제 시에는 다시 구독하고 백필해야 합니다. WebSocket은 `subscription.gap` 또는 `chain.reorg`와 같은 Push 제어 이벤트를 발생시키지 않습니다. Webhook의 경우 갭(누락 구간)은 범위 스캔이 필요하며, 리오그 알림은 자동으로 재전달되는 정식 이벤트를 유지하기 전에 대체된 이벤트를 표시하거나 폐기해야 합니다. [Push 리플레이](https://docs.blockvectra.com/en/guides/webhook-push/#delivery-retries-and-replay)는 보존된 일치 항목을 다시 보내는 것이며, 주소나 체인이 추가되기 전 또는 구독이 오프라인인 동안의 데이터를 복구하지는 않습니다. 복구 로직을 구현할 때는 [청구 규칙](https://docs.blockvectra.com/en/guides/billing-rules/)과 [오류 레퍼런스](https://docs.blockvectra.com/en/errors/)를 검토하세요.

## 사용 가능한 체인

`GET /v1/chains`에서 `ws`(불리언) 및 `subscriptions`(지원되는 유형의 배열)를 확인하여 특정 네트워크에서 WebSocket 구독이 활성화되어 있는지 확인할 수 있습니다.

아래 표는 WebSocket 지원이 활성화된 네트워크를 보여줍니다:

| 체인 | WebSocket 엔드포인트 (경로 키) |
| --- | --- |
| 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는 두 가지 방법으로 제공할 수 있습니다:

* **경로 키**: `wss://api.blockvectra.com/v1/{chain}/{api_key}`
* **헤더 키**: HTTP Upgrade 핸드셰이크 중에 `x-api-key: {api_key}` 또는 `Authorization: Bearer {api_key}` 헤더와 함께 `wss://api.blockvectra.com/v1/{chain}` 호출.

경로 키를 사용하는 경우 경로 키가 사용되며 두 인증 헤더는 모두 무시됩니다. 경로 키가 없으면 비어 있지 않은 `x-api-key`가 `Authorization: Bearer`보다 우선합니다. 브라우저 WebSocket API는 이러한 헤더를 설정할 수 없으므로 경로 키 URL을 사용하세요.

### 핸드셰이크 승인 검사

핸드셰이크는 다음과 같은 이유로 실패할 수 있습니다:

* **인증**: API key가 없으면 HTTP 401([`missing_api_key`](https://docs.blockvectra.com/en/errors/#missing_api_key)), 알 수 없거나 비활성화되거나 해지된 API key는 HTTP 401([`invalid_api_key`](https://docs.blockvectra.com/en/errors/#invalid_api_key)), 인증 서비스를 일시적으로 사용할 수 없는 경우 HTTP 503([`auth_unavailable`](https://docs.blockvectra.com/en/errors/#auth_unavailable))을 반환합니다.
* **계정 잔액**: 선불 잔액이 0 이하인 계정은 HTTP 402([`balance_exhausted`](https://docs.blockvectra.com/en/errors/#balance_exhausted)), 청구 상태를 확인할 수 없는 경우 HTTP 503([`billing_unavailable`](https://docs.blockvectra.com/en/errors/#billing_unavailable))을 반환합니다.
* **연결 한도**: 키당 한도(20개 연결) 또는 계정당 한도(50개 연결)를 초과하면 HTTP 429([`ws_connection_limit`](https://docs.blockvectra.com/en/errors/#ws_connection_limit))를 반환합니다.
* **체인 가용성**: 알 수 없거나 지원되지 않는 체인을 요청하면 HTTP 404([`unknown_chain`](https://docs.blockvectra.com/en/errors/#unknown_chain))를 반환합니다.
* **서버 용량**: 서버가 바쁘거나 과부하 상태일 때 핸드셰이크는 `Retry-After` 헤더와 함께 HTTP 503([`overloaded`](https://docs.blockvectra.com/en/errors/#overloaded))을 반환합니다.

연결이 완료되면 클라이언트는 표준 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/en/guides/billing-rules/)을 따릅니다.
* `newHeads` 알림은 해당 연결에 활성화된 `newHeads` 구독 수와 관계없이 연결당 블록 해시당 한 번만 계산됩니다.
* `logs` 알림은 일치하는 로그가 있는 블록 해시 및 단계(phase)별로 구독당 한 번만 계산됩니다. 일치하는 로그가 없는 블록은 과금되지 않습니다. 동일한 블록과 단계에서 여러 개의 로그가 일치하더라도 청구 요금이 배가되지 않습니다. 필터가 중복되더라도 별개의 구독은 각각 따로 계산됩니다. 체인 재구성 로그(`removed: true`)는 별도의 단위를 구성하며, 동일한 높이의 대체 블록은 다른 해시를 가지므로 서로 다른 단위입니다.
* 알림은 소켓 전송 버퍼로 성공적으로 플러시된 후에만 과금됩니다. 대기 중이거나 플러시되지 않고 폐기된 알림은 과금되지 않습니다. `eth_unsubscribe` 응답 전에 대기열에 들어간 알림은 플러시된 경우 과금 대상에 포함됩니다. WebSocket 메시지는 HTTP 청구 헤더를 포함하지 않으므로 측정된 CU는 계정 사용량에서 확인하세요.

## 구독 메서드

이 API는 표준 Ethereum pub/sub 인터페이스인 `eth_subscribe` 및 `eth_unsubscribe`를 구현합니다.

### `newHeads`

새 블록이 체인 헤드에 추가될 때마다 새 블록 헤더 객체를 발생시킵니다.

* **구독 요청**:
  ```json
  {"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}
  ```
* **구독 응답**: 불투명한 16진수 구독 식별자를 반환합니다:
  ```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/en/errors/#logs_filter_required))와 함께 거부됩니다.

* **필터 한도**: 최대 100개의 주소, 최대 4개의 토픽 위치(위치당 최대 16개의 후보 해시).

* **필터 용량**: 활성 로그 필터가 한도에 도달하면 구독은 오류 코드 `-32022`([`ws_filter_capacity`](https://docs.blockvectra.com/en/errors/#ws_filter_capacity))를 반환합니다.

* **체인 재구성(reorg)**: 체인 리오그로 인해 블록이 제거된 경우 제거된 로그에 대한 알림에는 `"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)**

`createPublicClient` 및 `webSocket` 트랜스포트를 통해 [viem](https://viem.sh) v2를 사용하여 연결합니다. `{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. Subscribe to new block headers (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. Subscribe to contract event logs (filter requires address or 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
# Connect with path-based API key using websocat
websocat "wss://api.blockvectra.com/v1/{chain}/{api_key}"

# Alternatively, pass the key via request header
websocat -H="x-api-key: {api_key}" "wss://api.blockvectra.com/v1/{chain}"

# Or connect using 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 code)와 짧은 원인이 포함된 Close 프레임을 전송합니다. 아래 표는 서버에서 발생시키는 종료 코드와 권장 조치를 나열합니다:

|                    종료 코드 | 원인 문자열                           | 설명                                                                                      | 재시도 가능 | 클라이언트 조치                                                                                                                                 |
| -----------------------: | -------------------------------- | --------------------------------------------------------------------------------------- | :----: | ---------------------------------------------------------------------------------------------------------------------------------------- |
| [1001](https://docs.blockvectra.com/en/errors/#1001) | `idle`                           | 3600초(1시간) 동안 구독이나 메시지가 없는 비활성 연결                                                       |    예   | 필요 시 다시 연결합니다.                                                                                                                           |
| [1003](https://docs.blockvectra.com/en/errors/#1003) | `binary frames are not accepted` | 바이너리 WebSocket 프레임 수신됨(UTF-8 텍스트 프레임만 허용)                                               |   아니오  | 자동으로 다시 연결하지 마세요. 클라이언트가 텍스트 프레임을 보내도록 수정합니다.                                                                                            |
| [1009](https://docs.blockvectra.com/en/errors/#1009) | `message too large`              | 인바운드 페이로드가 1 MiB를 초과함                                                                   |   아니오  | 자동으로 다시 연결하지 마세요. 대용량 요청을 분할하거나 페이로드 크기를 줄입니다.                                                                                           |
| [1012](https://docs.blockvectra.com/en/errors/#1012) | `service restart`                | 서버 재시작 중이거나 세션이 최대 수명(24시간)에 도달함                                                        |    예   | 무작위 지터 백오프로 다시 연결하고, 구독을 다시 설정하며, 누락된 데이터를 백필합니다.                                                                                        |
| [1013](https://docs.blockvectra.com/en/errors/#1013) | `chain unavailable`              | 체인을 사용할 수 없음                                                                            |    예   | 풀 지터 지수 백오프로 다시 연결하고, 구독을 다시 설정하며, 누락된 데이터를 백필합니다.                                                                                       |
| [1013](https://docs.blockvectra.com/en/errors/#1013) | `overloaded`                     | 서버 일시적 과부하                                                                              |    예   | 풀 지터 지수 백오프로 다시 연결하고, 구독을 다시 설정하며, 누락된 데이터를 백필합니다.                                                                                       |
| [4402](https://docs.blockvectra.com/en/errors/#4402) | `insufficient balance`           | 계정 잔액 소진                                                                                |   아니오  | 자동으로 다시 연결하지 마세요. [잔액을 충전한 후 다시 연결하세요](https://docs.blockvectra.com/en/guides/billing-rules/).                                                                       |
| [4404](https://docs.blockvectra.com/en/errors/#4404) | `invalid api key`                | API key를 알 수 없거나 비활성화되었거나 해지됨                                                           |   아니오  | 자동으로 다시 연결하지 마세요. 다시 연결하기 전에 콘솔에서 API key를 확인하거나 순환하세요.                                                                                  |
| [4408](https://docs.blockvectra.com/en/errors/#4408) | `slow consumer`                  | 푸시 큐가 512 KiB를 초과하여 대기 중인 알림을 버리고 세션을 닫음; 클라이언트에 Close 프레임이 전달되지 않을 수 있음(브라우저는 1006 보고) |    예   | 예기치 않은 연결 끊김(Close 프레임 미수신, 브라우저가 1006 보고)을 4408처럼 처리하세요: 백오프로 재연결하고, 구독을 다시 설정하며, `eth_getLogs`로 누락된 데이터를 백필합니다. 구독 수를 줄이거나 더 빠르게 읽으세요. |
| [4429](https://docs.blockvectra.com/en/errors/#4429) | `push rate exceeded`             | 알림 발생 속도가 초당 1,000회를 초과함                                                                |    예   | 구독을 줄이거나 필터를 좁히세요. 백오프로 다시 연결하고, 다시 구독하며, 백필합니다.                                                                                         |
| [4503](https://docs.blockvectra.com/en/errors/#4503) | `billing unavailable`            | 청구 서비스 일시적 사용 불가                                                                        |    예   | 일시적 상태입니다. 풀 지터 지수 백오프로 다시 연결하세요.                                                                                                        |

## 재연결 및 지수 백오프

연결이 끊어졌을 때 동기화된 재연결 폭풍(thundering herd)을 방지하려면 클라이언트는 풀 지터(full jitter)가 적용된 지수 백오프를 구현해야 합니다:

* **백오프 공식**: n번째 재연결 시도(n = 0, 1, 2, ...) 전에 균등 무작위로 선택된 대기 시간을 갖습니다:
  ```
  delay = random(0, min(20s, 0.5s * 2^n))
  ```
* **카운터 리셋**: 최소 `60초` 동안 중단 없는 안정적인 연결을 유지한 후에만 재시도 카운터 n을 0으로 리셋합니다.
* **종료 코드 1012**: 동기화된 재연결 스파이크를 방지하기 위해 첫 번째 재연결 시도 전에 무작위 초기 지연을 도입합니다.
* **재시도 불가 코드**: [4402](https://docs.blockvectra.com/en/errors/#4402), [4404](https://docs.blockvectra.com/en/errors/#4404), [1003](https://docs.blockvectra.com/en/errors/#1003) 또는 [1009](https://docs.blockvectra.com/en/errors/#1009) 발생 시 자동으로 다시 연결하지 마세요.

### 재연결 후 누락된 데이터 백필

WebSocket 구독은 연결 간에 유지되지 않으며, 연결이 끊어진 동안 발생한 알림은 서버에 보존되지 않습니다. 재연결 후 클라이언트는 다음 캐치업 전략을 실행해야 합니다:

1. **`eth_getLogs`로 로그 백필**:
   * 성공적으로 처리된 가장 높은 블록 번호(`last_processed_block`)를 영구 저장합니다.
   * 실시간 이벤트를 캡처하기 위해 재연결 즉시 `eth_subscribe("logs", ...)`를 호출합니다.
   * `fromBlock: last_processed_block + 1` 및 `toBlock: "latest"`(또는 실시간 스트림에서 수신된 첫 번째 블록)로 `eth_getLogs`를 통해 누락된 블록을 쿼리합니다.
   * 연결 해제 갭이 네트워크의 `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/en/errors/#subscription_limit)     |
| WebSocket 연결당 `newHeads` 구독 수 | 4                                                    | `-32022` [`subscription_limit`](https://docs.blockvectra.com/en/errors/#subscription_limit)     |
| `logs` 구독 필터 요구사항             | `address` 또는 `topic0`(`topics`의 첫 번째 위치)을 반드시 지정해야 함 | `-32602` [`logs_filter_required`](https://docs.blockvectra.com/en/errors/#logs_filter_required) |

## 다음 단계

* [데이터셋 디렉터리 둘러보기](https://blockvectra.com/en/data/): BlockVectra가 인덱싱하는 모든 데이터셋 확인.
* [무료 플랜 및 요금 확인](https://blockvectra.com/en/pricing/#free): 계정에 포함된 혜택 확인.
* [콘솔 로그인](https://console.blockvectra.com/login/?next=%2Fkeys%2F): API key 생성.
