# 블록체인 Webhook 연동: 서명 검증, 중복 제거 및 리플레이

> Source: https://docs.blockvectra.com/ko/guides/webhook-push/

EVM 지갑 주소를 감시하고 네이티브 코인 전송, 토큰 전송 및 일치하는 컨트랙트 로그를 HTTPS 엔드포인트로 수신하여 지갑 활동 알림이나 스마트 컨트랙트 이벤트를 모니터링할 수 있습니다. 개발자와 AI Agent는 동일한 HTTP 구독 API를 사용합니다. ERC-20 USDT / USDC 결제 입금 알림의 경우 [스테이블코인 결제 수신 가이드](https://docs.blockvectra.com/en/guides/stablecoin-payments/#receive-payments-with-webhooks)를 참고하세요.

## 이 가이드를 통해 완료할 수 있는 작업

* [지갑 주소 활동 수신](#지갑-주소-활동-연동): 인증된 구독을 생성하고, 감시 주소를 추가하며, 수신된 이벤트를 검증합니다.
* [일치하는 컨트랙트 로그 모니터링](#이벤트-형식): 감시 주소의 `log` 이벤트를 검사하고 수신 엔드포인트에서 `address`, `topics`, `data`를 필터링합니다.
* [중단된 전달 복구](#전달-재시도-및-리플레이): 구독 진행 상태를 확인하고 보존된 일치 항목을 리플레이한 후, 리플레이 윈도우 외부의 갭(누락 구간)을 백필합니다.

구독은 하나의 HTTPS 수신 URL, 서명 시크릿, 감시 대상 EVM 주소 및 필수 `chains` 객체로 구성됩니다. 주소는 해당 객체의 모든 체인에 적용됩니다. `x-api-key` 헤더를 포함하여 API를 호출하며, 계정의 모든 활성 키는 계정 내 모든 구독을 관리할 수 있습니다. 시작하기 전에 [API key를 발급](https://blockvectra.com/en/get-api-key/)받으세요. 모든 작업 및 webhook 스키마는 [Push OpenAPI](https://docs.blockvectra.com/openapi/push.yaml)에 명시되어 있습니다.

<span id="connect-wallet-address-activity" />

## 지갑 주소 활동 연동

1. [요청 원본 본문을 검증](#verify-signatures)하고 이벤트 `id` 기준으로 영구 저장하며 10초 이내에 승인(응답)하는 수신 엔드포인트를 배포합니다.
2. `GET /v1/push/chains`를 조회한 다음 HTTPS URL과 선택한 체인으로 [구독을 생성](#create-a-subscription)합니다. 반환된 `id`와 `secret`을 저장합니다.
3. [지갑 주소를 추가](#add-and-list-addresses)합니다. `applied_version >= change_version`이 될 때까지 대기하고 각 체인의 `applied_from_block`을 기록합니다. 이 블록부터 매칭이 시작됩니다.
4. 전송 및 로그를 처리하고 [갭(누락) 또는 교체된 블록을 복구](#delivery-retries-and-replay)합니다. 결제 처리에서 알림을 사용하기 전에 토큰 컨트랙트, 수신 주소 및 정수 금액을 필터링하세요.

## Webhook, WebSocket 또는 폴링 선택

* **Webhook**: 감시 대상 주소의 이벤트를 HTTPS 수신 엔드포인트로 전송하며, 전송 재시도 및 보존된 일치 항목의 리플레이를 지원합니다.
* **[WebSocket](https://docs.blockvectra.com/en/guides/websocket-subscriptions/)**: 영구 연결을 통해 `newHeads` 및 필터링된 `logs`를 스트리밍합니다. 연결이 끊어지면 다시 연결하고 재구독하며 누락된 블록을 조회해야 합니다.
* **[폴링](https://docs.blockvectra.com/en/guides/stablecoin-payments/)**: 자체 커서를 사용하여 유한한 블록 범위에서 `eth_getLogs`를 쿼리합니다. 결제 모니터링이나 누락된 로그 백필에 사용됩니다.

WebSocket 지원 여부는 `GET /v1/chains`의 `ws` 및 `subscriptions`를 확인하세요. `ws`가 false이더라도 해당 체인이 인증된 `GET /v1/push/chains` 목록에 표시되면 주소 Webhook을 계속 사용할 수 있습니다. RPC 지원만으로는 Push 지원 여부를 확정할 수 없습니다.

## 주소 수용 한도

셀프서비스는 구독당 최대 1,000,000개의 주소를 지원하며 회원가입 즉시 이용할 수 있습니다. 하나의 구독으로 단일 수신 URL에서 여러 체인을 처리할 수 있습니다. 엔터프라이즈 플랜은 구독당 10,000,000개 / 100,000,000개의 주소를 지원합니다. [활성화 문의](https://blockvectra.com/en/contact/)를 통해 신청하세요. 개발자와 AI Agent는 동일한 용량 옵션과 가격을 적용받습니다. 두 계층 모두 [가격 정책](https://blockvectra.com/en/pricing/)에 명시된 동일한 주소-일 및 전달된 이벤트 단가를 사용합니다.

<span id="create-a-subscription" />

## 구독 생성

`GET /v1/push/chains`를 호출하여 사용 가능한 체인과 최소, 기본, 최대 확인 수(confirmation count)를 확인하세요. `head - block + 1 >= confirmations` 조건을 만족할 때 블록이 릴리스됩니다. 각 체인은 `{}`를 전달하여 기본값을 사용할 수 있습니다. 하나 이상의 체인을 지정해야 하며, 새로 추가된 체인은 기존 구독에 자동으로 포함되지 않습니다.

다음 예시를 `create.json`으로 저장하고, URL을 실제 수신 엔드포인트로 변경한 후 체인 목록에서 원하는 체인을 선택하세요. URL은 포트 443의 HTTPS를 사용해야 하며, IP 리터럴이 아닌 호스트 이름이어야 하고, 사용자 정보나 프래그먼트(#)를 포함할 수 없습니다.

```json
{
  "url": "https://hooks.example.com/push",
  "chains": {
    "bsc_mainnet": {
      "confirmations": 1
    },
    "base_mainnet": {}
  }
}
```

환경 변수에 `BLOCKVECTRA_API_KEY`를 설정한 후 다음을 실행하세요:

```bash
PUSH_URL='https://api.blockvectra.com/v1/push'
curl --fail-with-body -sS "$PUSH_URL/chains" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
curl --fail-with-body -sS "$PUSH_URL/subscriptions" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d @create.json > subscription.json
```

생성에 성공하면 HTTP 201과 주소가 비어 있는 `online` 상태의 구독이 반환됩니다. 숫자 `id`와 `secret`을 안전하게 보관하세요. 시크릿은 생성 시점 또는 `POST /subscriptions/{subscription_id}/rotate-secret` 호출 시에만 반환됩니다. 시크릿 순환(rotation)은 모든 체인에 즉시 적용되며 이전 시크릿과의 중복 허용 기간은 없습니다. 생성 직후 테스트 메시지는 전송되지 않습니다.

<span id="add-and-list-addresses" />

## 주소 추가 및 조회

주소 배치를 `addresses.json`으로 저장하고, 예시 주소를 감시할 실제 주소로 변경하세요:

```json
{
  "addresses": [
    "0x9725db73f2cd8657f3e1841e5689f210ee54a92d",
    "0x99d47bB552ae095159C251836De6A5d524076872"
  ]
}
```

`SUBSCRIPTION_ID`를 반환받은 구독 ID로 설정하세요:

```bash
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID/addresses/add" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -H 'Content-Type: application/json' -d @addresses.json
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID/addresses" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
```

각 주소 추가 호출은 최대 10,000개의 주소를 수용합니다. 입력 주소는 소문자이거나 유효한 EIP-55 혼합 대소문자여야 합니다. 유효하지 않은 입력이 포함되면 배치 전체가 거부됩니다. 이미 존재하는 주소는 `unchanged`로 계산되므로 동일한 추가 요청을 재전송해도 안전합니다. 주소 목록 조회는 `limit` 및 `page_token`을 사용하며, `next_page_token: null`은 마지막 페이지를 나타냅니다.

주소를 추가하면 `change_version`이 반환됩니다. `applied_version >= change_version`이 될 때까지 `GET /subscriptions/{subscription_id}`를 폴링하거나 확인하세요. 변경 사항은 일반적으로 적용되는 데 약 1초가 소요됩니다. 각 체인의 `applied_from_block`은 온체인 트랜잭션 및 로그 매칭이 시작되는 유효 블록을 나타냅니다. 새 주소는 과거 이벤트에 대해 소급하여 매칭되지 않습니다.

구독 생성 시 반환되는 HTTP 201은 구독 리소스가 성공적으로 생성되었음을 확인하는 것일 뿐이며, 수신 엔드포인트가 Webhook 푸시를 수신했음을 의미하지는 않습니다. 플랫폼은 생성 시점이나 주소 등록 시 검증 또는 테스트 메시지를 발송하지 않습니다. 감시 대상 주소 및 체인에서 일치하는 온체인 활동이 실제로 발생할 때까지 기다려야 수신 엔드포인트에서 전달 여부를 검증할 수 있습니다.

<span id="event-format" />

## 이벤트 형식

모든 POST 요청은 `type: push.events`, `created_at`, `data`를 포함합니다. `data`에는 `subscription_id`, 단일 `chain`, `complete_through_block` 및 `events`가 포함됩니다. 체인별로 진행 상황을 기록하세요. 동일한 블록이 여러 메시지에 걸쳐 전달될 수 있으므로 개별 이벤트의 블록 번호는 전체 블록 완료 마커가 아닙니다. 각 메시지에는 최대 1,000개의 이벤트, 1 MiB 및 50개의 블록이 포함됩니다.

```json
{
  "type": "push.events",
  "created_at": "2026-10-02T03:00:05Z",
  "data": {
    "subscription_id": 48213,
    "chain": "bsc_mainnet",
    "complete_through_block": 64000121,
    "events": [
      {
        "id": "evt_payvsqb6ogymhmehrs2wl5xcky",
        "type": "native.transfer",
        "ref": "eip155:56:0x7b19944dc683c33e8dedba259cb6939f7271f70f2eeb6ca5e456cccb57cc1eff:tx",
        "from": "0xe0a2100d7dad33f70c4bb765323cb96b2400c844",
        "to": "0x9725db73f2cd8657f3e1841e5689f210ee54a92d",
        "amount": "150000000000000000",
        "block_number": 64000120,
        "block_hash": "0x327892a3e5699a43981f0fbcc5e490628641d92c040eb0429fb550ba3a73c3bf",
        "block_timestamp": "2026-10-02T03:00:00Z",
        "tx_hash": "0x7b19944dc683c33e8dedba259cb6939f7271f70f2eeb6ca5e456cccb57cc1eff",
        "tx_index": 3,
        "matched": [
          {
            "address": "0x9725db73f2cd8657f3e1841e5689f210ee54a92d",
            "role": "to"
          }
        ]
      },
      {
        "id": "evt_lgcdattb6l2k3ejuhe4mtdljkm",
        "type": "token.transfer",
        "ref": "eip155:56:0x3be3448e6f54ebfa928d5997a0cb2d5e9d3186c5ebfa293ad45b7edc72483a76:7",
        "standard": "erc20",
        "token": "0x55d398326f99059ff775485246999027b3197955",
        "from": "0x0f94e5283c41c29a8f4dff8c17f68bdfb59f07df",
        "to": "0x99d47bb552ae095159c251836de6a5d524076872",
        "token_id": null,
        "amount": "25000000000000000000",
        "batch_index": null,
        "block_number": 64000121,
        "block_hash": "0x6a8146159162f182c091d17eac7d03e95dc92ce80de704c958ca2528306aff15",
        "block_timestamp": "2026-10-02T03:00:00Z",
        "tx_hash": "0x3be3448e6f54ebfa928d5997a0cb2d5e9d3186c5ebfa293ad45b7edc72483a76",
        "tx_index": 5,
        "log_index": 7,
        "matched": [
          {
            "address": "0x99d47bb552ae095159c251836de6a5d524076872",
            "role": "to"
          }
        ]
      },
      {
        "id": "evt_sgliw3ficdf6gaa6zzx4ew6vni",
        "type": "log",
        "ref": "eip155:56:0x3be3448e6f54ebfa928d5997a0cb2d5e9d3186c5ebfa293ad45b7edc72483a76:8",
        "address": "0xb54ffbe723264b84cf74947127a6914cf87fc593",
        "topics": [
          "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
          "0x00000000000000000000000099d47bb552ae095159c251836de6a5d524076872",
          "0x000000000000000000000000b54ffbe723264b84cf74947127a6914cf87fc593"
        ],
        "data": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "block_number": 64000121,
        "block_hash": "0x6a8146159162f182c091d17eac7d03e95dc92ce80de704c958ca2528306aff15",
        "block_timestamp": "2026-10-02T03:00:00Z",
        "tx_hash": "0x3be3448e6f54ebfa928d5997a0cb2d5e9d3186c5ebfa293ad45b7edc72483a76",
        "tx_index": 5,
        "log_index": 8,
        "matched": [
          {
            "address": "0x99d47bb552ae095159c251836de6a5d524076872",
            "role": "topic1"
          }
        ]
      }
    ]
  }
}
```

| 이벤트 유형             | 처리 내용                                                                                                                                                       |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `native.transfer`  | 감시 대상 주소와 관련된 성공한 최상위 네이티브 코인 전송입니다. `amount`는 정수 10진수 문자열입니다. 내부 네이티브 전송은 제외됩니다.                                                                           |
| `token.transfer`   | 감시 대상 주소와 관련된 ERC-20, ERC-721 및 ERC-1155 전송입니다. `standard`, `token`, `token_id`, `amount`, `batch_index`를 확인하세요. ERC-1155 배치 전송은 항목당 하나의 이벤트를 생성합니다.        |
| `log`              | 감시 대상 주소가 발행 컨트랙트이거나 topics 1–3에 포함된 기타 로그입니다. `address`, `topics`, `data`, `matched`를 확인하세요.                                                               |
| `subscription.gap` | `from_block`부터 `to_block`까지의 구간을 전달할 수 없는 경우이며, `reason: retention_expired`가 포함됩니다. Data API 또는 `eth_getLogs`로 백필하세요.                                       |
| `chain.reorg`      | 무료 리오그(reorg) 알림: 이미 전달된 `from_block`–`to_block` 구간의 블록이 교체되었습니다. `ref`를 기준으로 이전 이벤트를 표시하거나 폐기하고, 이후 자동으로 재전달되는 정규 체인(canonical) 이벤트를 유지하며 `id`로 중복을 제거하세요. |

동일한 구독 내에서는 이벤트 `id`로 중복을 제거하고, 여러 구독 간에는 `ref`와 `type`을 사용하세요. 알 수 없는 필드와 이벤트 유형은 무시하세요. 금융 관련 작업을 수행하기 전에 온체인 사실을 직접 검증하세요.

<span id="verify-signatures" />

## 서명 검증

요청 헤더는 `webhook-id`, `webhook-timestamp`, `webhook-signature`, `bv-subscription-id`입니다. 직접 생성한 구독의 시크릿만 선택하고 알 수 없는 ID는 거부하세요. JSON을 파싱하기 전에 요청 원본 본문(raw body)의 바이트를 사용하여 `webhook-id.webhook-timestamp.raw-body`에 대한 HMAC-SHA256을 검증하세요. 서명 형식은 `v1,<base64>`이며, 약 5분의 타임스탬프 오차를 허용하고 상수 시간(constant-time) 비교를 수행하세요.

다음 Node.js 함수는 원본 본문을 `Buffer`로, 요청 헤더를 객체로, 구독 ID와 저장된 시크릿의 맵을 받습니다:

```js
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyPush(rawBody, headers, secrets) {
  const subscriptionId = headers['bv-subscription-id'];
  const id = headers['webhook-id'];
  const timestamp = headers['webhook-timestamp'];
  const signature = headers['webhook-signature'];
  if ([subscriptionId, id, timestamp, signature].some(v => typeof v !== 'string')) return false;
  const secret = secrets.get(subscriptionId);
  if (typeof secret !== 'string' || !secret.startsWith('whsec_')) return false;
  if (!/^\d{10}$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const match = /^v1,([A-Za-z0-9+/]{43}=)$/.exec(signature);
  if (!match) return false;
  const received = Buffer.from(match[1], 'base64');
  const expected = createHmac('sha256', Buffer.from(secret.slice(6), 'base64'))
    .update(`${id}.${timestamp}.`).update(rawBody).digest();
  return received.length === expected.length && timingSafeEqual(received, expected);
}
```

검증 후 본문을 파싱하고, 처리 결과를 영구 저장한 뒤 10초 이내에 2xx를 반환하세요. 구독 ID 헤더는 서명이 검증되기 전까지 신뢰할 수 없습니다.

## 첫 번째 이벤트 검증

구독을 online 상태로 유지하세요. 주소 변경이 적용된 후 일치하는 온체인 활동이 발생할 때까지 기다렸다가 수신 엔드포인트가 이벤트를 검증하고 영구적으로 저장하는지 확인하세요.

## 검증 후 수신 중단 및 정리

<a id="stop-listening-and-clean-up" />

주소 감시를 중단하려면 제거할 주소를 `addresses.json`에 저장하고 `POST /subscriptions/{subscription_id}/addresses/remove`를 호출하세요:

```bash
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID/addresses/remove" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -H 'Content-Type: application/json' -d @addresses.json
```

각 주소 제거 호출은 최대 10,000개의 주소를 수용합니다. 현재 감시 중이 아닌 주소는 `unchanged`로 계산됩니다. 호출은 `change_version`을 반환합니다. `applied_version >= change_version`이 되면 해당 유효 블록 이후의 블록은 제거된 주소와 더 이상 매칭되지 않습니다. 이전에 이미 매칭된 이벤트(전송 중, 재시도 중 또는 대기열에 있는 이벤트)는 계속 전달되며, 이미 전달된 이벤트는 철회되지 않습니다.

설정이나 주소를 삭제하지 않고 수신을 일시 중지하려면 `status`를 `offline`으로 설정하세요:

```bash
curl --fail-with-body -sS -X PATCH "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"offline"}'
```

`offline` 상태의 구독은 수신 및 전달을 중단하고 매칭 인덱스에서 주소를 언로드하며, 전체 UTC 일자 동안 오프라인 상태를 유지한 날에는 주소 요금이 부과되지 않습니다. 모든 설정(URL, 시크릿, 주소, 체인 및 확인 수)은 그대로 보존됩니다. `{"status":"online"}`으로 패치하면 현재 유효 블록부터 수신을 재개하며 오프라인 기간의 이벤트는 백필하지 않습니다.

`PATCH /subscriptions/{subscription_id}`에 JSON Merge Patch를 사용하여 `url`, `key_id`, `status` 또는 `chains`를 변경할 수 있습니다. 체인 객체는 추가 또는 업데이트를 의미하고, `null`은 체인을 제거합니다. 최소 하나의 체인은 유지되어야 합니다. 구독을 영구적으로 삭제하려면 다음을 실행하세요:

```bash
curl --fail-with-body -sS -X DELETE "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
```

`DELETE`는 구독을 영구적으로 제거하고, 모든 체인에서의 전달을 즉시 중단하며, 시크릿과 주소를 완전히 삭제합니다.

<span id="delivery-retries-and-replay" />

## 전달, 재시도 및 리플레이

전달 시맨틱은 최소 한 번(at-least-once)입니다. 각 구독의 체인은 블록 및 블록 내 위치 순서대로 정렬되며, 실패한 배치는 해당 체인의 후속 이벤트를 차단합니다. 서로 다른 체인은 독립적인 진행 상태를 가지며 동시에 POST 요청을 보낼 수 있습니다. 동일한 배치의 재시도는 `webhook-id`를 유지하지만, 변경된 배치는 새 ID를 가질 수 있습니다. 배치가 아닌 개별 이벤트 단위로 중복을 제거하세요.

10초 이내에 반환된 모든 2xx는 영구 처리 완료를 의미합니다. 리디렉션은 따르지 않으며, 3xx 및 410은 실패로 처리됩니다. 실패 후 재시도는 즉시, 5초, 30초, 2분, 10분, 30분, 1시간 간격으로 진행되며 이후 매시간 재시도합니다. 429의 `Retry-After`는 대기 시간을 최대 1시간까지 연장할 수 있습니다. 전달이 중단되면 각 체인의 `condition`, `last_error`, `next_attempt_at`을 확인하세요. 상태 조건은 `receiver_failing`, `insufficient_balance`, `key_revoked`가 있으며, 마지막 항목의 경우 계정의 다른 활성 키로 `key_id`를 패치해야 합니다.

전달되지 않은 이벤트는 보존 기간(retention window)을 벗어나면 만료되며 `subscription.gap`을 생성합니다. `POST /subscriptions/{subscription_id}/replay`는 `chain`과 `from_block`을 파라미터로 받습니다. `GET /push/chains`의 `replayable_from_block`과 구독의 진행 상태를 참조하세요. 리플레이는 기존 일치 항목을 전달하며, 주소나 체인이 추가되기 전의 이벤트는 복구할 수 없습니다.

`chain.reorg`는 이미 전달된 블록이 교체되었음을 알리며, 전달 누락을 의미하지는 않습니다. 설정한 확인 수보다 얕은 리오그는 수신 측에 노출되지 않습니다. 최대 1,024개 블록 깊이까지 전달된 블록에 영향을 미치는 리오그의 경우, 정규 체인 이벤트가 새 `id`와 함께 자동으로 재전달됩니다. `ref`를 기준으로 교체된 이벤트를 표시하거나 폐기하고, 정규 체인 이벤트를 유지하며 `id`로 중복을 제거하세요. 결제 기록의 경우 `ref`와 `tx_hash`로 대사를 수행하세요. 이보다 더 깊은 리오그가 발생하면 해당 체인이 중단됩니다. `GET /push/chains`의 `halted`를 확인하세요. 체인이 복구된 후 정규 체인 재전달이 이어집니다. 이 제어 이벤트는 `complete_through_block`을 전진시키지 않습니다.

`GET /subscriptions/{subscription_id}/events?chain=...`로 전달된 데이터 이벤트를 조회할 수 있으며, 선택적으로 `from_block`, `to_block`, `limit`, `page_token`을 추가할 수 있습니다. 이력 행에는 `event`, `replay_epoch`, `orphaned`, `delivered_at`이 포함됩니다. `orphaned: true`는 나중에 교체된 블록을 나타냅니다. 이력 조회 진입 시 402 `insufficient_balance`(`data.reason`: `balance_exhausted` 또는 `free_grant_exhausted`), 403 `key_cap_exhausted`(`data.cu_cap`), 또는 429 `rate_limited`(`key_rate_limit` 또는 `free_plan_call_limit`)가 반환될 수 있습니다. 429 `cost_exceeds_burst`는 `request_exceeds_burst` 이유 및 `data.max`를 포함하므로 재시도 전에 버스트 용량을 늘려야 합니다. 유효하지 않은 범위 및 재시도 안내는 [오류 처리](https://docs.blockvectra.com/en/errors/)를 참조하세요.

## 과금 정책 및 예시

가중치는 `GET /v1/plans`에서 가져옵니다. 전달된 데이터 이벤트, 성공한 이력 조회 요청 및 주소-일은 별도의 가중치를 가집니다. 이력 조회를 제외한 관리 호출, 제어 이벤트, 실패한 전달 및 자동 재시도는 무료입니다. 전달된 각 이벤트는 1회 과금되며, 고객 리플레이 및 정규 체인 이벤트 재전달 시 새로운 전달 요금이 발생합니다.

주소 요금은 각 구독이 UTC 일자 중 online 상태였던 기간 동안의 최대 주소 수를 기준으로 하며, 여러 구독 간에 공유되는 계정 무료 주소 할당량을 먼저 차감합니다(먼저 생성된 구독 우선). 동일한 주소가 두 개의 구독에 등록된 경우 2회로 계산됩니다. 체인을 추가하면 이벤트 요금이 변경되며 주소 요금은 변경되지 않습니다. 전체 UTC 일자 동안 오프라인 상태였던 구독에는 주소 요금이 부과되지 않습니다.

| 용량/항목 | 과금 단위 | CU |
| --- | --- | --- |
| `push.address_day` | 과금 대상 주소-일 | 33 |
| `push.history` | 성공한 이력 조회 요청 | 25 |
| `push.log` | 전달된 데이터 이벤트 | 150 |
| `push.native_transfer` | 전달된 데이터 이벤트 | 150 |
| `push.token_transfer` | 전달된 데이터 이벤트 | 150 |

계정당 UTC 일일 무료 주소 수: 1000

플랜과 관계없이 모든 구독 그룹이 공유하는 계정당 UTC 일일 무료 주소 할당량입니다. 각 그룹에 대해 해당 일 동안 온라인 상태였던 최대 주소 수를 집계하며, 그룹 ID 오름차순으로 할당량을 배분합니다. 동일한 주소가 두 그룹에 포함되면 두 번 계산되며, 그룹 내 체인 수가 주소 수에 곱해지지는 않습니다. 하루 종일 오프라인이거나 삭제된 그룹은 포함되지 않습니다. 각 그룹별로 할당량에서 차감된 후 남은 주소 수에 `method_weights`의 `push.address_day` CU 가중치를 곱합니다. 현재 설정된 할당량은 주소-일 요금에 사용되는 요금 정책과 동일한 정책에서 가져오며, 계정 용량 한도나 그룹별 별도 할당량이 아닙니다.

예시: 전달된 native.transfer 이벤트 10개, 성공한 이력 조회 2회, 과금 대상 주소-일 10일의 비용은 10 × 150 + 2 × 25 + 10 × 33 = 1880 CU입니다. 과금 대상 주소-일은 계정 무료 주소 할당량을 공제한 후 계산됩니다.

CU 측정 및 환산에 대한 자세한 내용은 [과금 규칙](https://docs.blockvectra.com/en/guides/billing-rules/) 및 [가격 정책 페이지](https://blockvectra.com/en/pricing/)를 참조하세요.

## 관련 리소스

* 지원되는 이벤트, 체인 적용 범위 및 가격에 대한 비교는 [블록체인 Webhook API 개요](https://blockvectra.com/en/webhooks/)를 참조하세요.
