WebSocket 구독

BlockVectra WebSocket 엔드포인트에 연결하여 eth_subscribe newHeads 및 logs를 사용하세요. 연결 방식, 필터 규칙, 재연결 백오프 및 복구 방법을 안내합니다.

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

WebSocket, Webhook 또는 폴링 선택

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

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

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

사용 가능한 체인

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

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

체인WebSocket 엔드포인트 (경로 키)
Robinhood Chainwss://api.blockvectra.com/v1/robinhood_mainnet/{api_key}
Robinhood Chain Testnetwss://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), 알 수 없거나 비활성화되거나 해지된 API key는 HTTP 401(invalid_api_key), 인증 서비스를 일시적으로 사용할 수 없는 경우 HTTP 503(auth_unavailable)을 반환합니다.
  • 계정 잔액: 선불 잔액이 0 이하인 계정은 HTTP 402(balance_exhausted), 청구 상태를 확인할 수 없는 경우 HTTP 503(billing_unavailable)을 반환합니다.
  • 연결 한도: 키당 한도(20개 연결) 또는 계정당 한도(50개 연결)를 초과하면 HTTP 429(ws_connection_limit)를 반환합니다.
  • 체인 가용성: 알 수 없거나 지원되지 않는 체인을 요청하면 HTTP 404(unknown_chain)를 반환합니다.
  • 서버 용량: 서버가 바쁘거나 과부하 상태일 때 핸드셰이크는 Retry-After 헤더와 함께 HTTP 503(overloaded)을 반환합니다.

연결이 완료되면 클라이언트는 표준 JSON-RPC 2.0 요청(eth_blockNumber 또는 eth_call 등) 및 UTF-8 텍스트 프레임 형식의 구독 제어 메서드를 전송할 수 있습니다.

과금 규칙

  • 연결 수립, 유휴 상태 연결 유지 및 ping/pong 하트비트는 과금되지 않습니다.
  • 성공한 eth_subscribe 및 eth_unsubscribe 호출은 과금되며(false를 반환하는 구독 취소 포함), 실패한 호출은 과금되지 않습니다. 일반 JSON-RPC 호출은 JSON-RPC 청구 규칙을 따릅니다.
  • newHeads 알림은 해당 연결에 활성화된 newHeads 구독 수와 관계없이 연결당 블록 해시당 한 번만 계산됩니다.
  • logs 알림은 일치하는 로그가 있는 블록 해시 및 단계(phase)별로 구독당 한 번만 계산됩니다. 일치하는 로그가 없는 블록은 과금되지 않습니다. 동일한 블록과 단계에서 여러 개의 로그가 일치하더라도 청구 요금이 배가되지 않습니다. 필터가 중복되더라도 별개의 구독은 각각 따로 계산됩니다. 체인 재구성 로그(removed: true)는 별도의 단위를 구성하며, 동일한 높이의 대체 블록은 다른 해시를 가지므로 서로 다른 단위입니다.
  • 알림은 소켓 전송 버퍼로 성공적으로 플러시된 후에만 과금됩니다. 대기 중이거나 플러시되지 않고 폐기된 알림은 과금되지 않습니다. eth_unsubscribe 응답 전에 대기열에 들어간 알림은 플러시된 경우 과금 대상에 포함됩니다. WebSocket 메시지는 HTTP 청구 헤더를 포함하지 않으므로 측정된 CU는 계정 사용량에서 확인하세요.

구독 메서드

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

newHeads

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

  • 구독 요청:
    {"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}
  • 구독 응답: 불투명한 16진수 구독 식별자를 반환합니다:
    {"jsonrpc":"2.0","id":1,"result":"0x1"}
  • 푸시 알림 프레임:
    {"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)와 함께 거부됩니다.

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

  • 필터 용량: 활성 로그 필터가 한도에 도달하면 구독은 오류 코드 -32022(ws_filter_capacity)를 반환합니다.

  • 체인 재구성(reorg): 체인 리오그로 인해 블록이 제거된 경우 제거된 로그에 대한 알림에는 "removed": true가 포함됩니다.

  • 구독 요청:

    {"jsonrpc":"2.0","id":2,"method":"eth_subscribe","params":["logs",{"address":"0x1234567890123456789012345678901234567890"}]}

eth_unsubscribe

구독 식별자를 사용하여 활성 구독을 종료합니다.

  • 구독 취소 요청:
    {"jsonrpc":"2.0","id":3,"method":"eth_unsubscribe","params":["0x1"]}
  • 구독 취소 응답:
    {"jsonrpc":"2.0","id":3,"result":true}

실행 가능한 예제

createPublicClient 및 webSocket 트랜스포트를 통해 viem v2를 사용하여 연결합니다. {chain}을 대상 체인 식별자로, {api_key}를 실제 API key로 바꾸세요:

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);
  },
});

종료 코드 및 클라이언트 대응 조치

서버가 WebSocket 세션을 종료할 때 특정 종료 코드(close code)와 짧은 원인이 포함된 Close 프레임을 전송합니다. 아래 표는 서버에서 발생시키는 종료 코드와 권장 조치를 나열합니다:

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

재연결 및 지수 백오프

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

  • 백오프 공식: n번째 재연결 시도(n = 0, 1, 2, ...) 전에 균등 무작위로 선택된 대기 시간을 갖습니다:
    delay = random(0, min(20s, 0.5s * 2^n))
  • 카운터 리셋: 최소 60초 동안 중단 없는 안정적인 연결을 유지한 후에만 재시도 카운터 n을 0으로 리셋합니다.
  • 종료 코드 1012: 동기화된 재연결 스파이크를 방지하기 위해 첫 번째 재연결 시도 전에 무작위 초기 지연을 도입합니다.
  • 재시도 불가 코드: 4402, 4404, 1003 또는 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
WebSocket 연결당 newHeads 구독 수4-32022 subscription_limit
logs 구독 필터 요구사항address 또는 topic0(topics의 첫 번째 위치)을 반드시 지정해야 함-32602 logs_filter_required

다음 단계

최종 수정일:

이 페이지의 내용