# Configurar Webhooks de blockchain: assinaturas, deduplicação e replay

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

Monitore um endereço de carteira EVM e receba transferências nativas, transferências de tokens e logs de contratos correspondentes no seu endpoint HTTPS, para notificações de atividade da carteira ou monitoramento de eventos de smart contracts. Desenvolvedores e agentes de IA usam a mesma API HTTP de assinaturas. Para notificações de pagamentos ERC-20 em USDT / USDC, siga o [receptor de pagamentos com stablecoins](https://docs.blockvectra.com/en/guides/stablecoin-payments/#receive-payments-with-webhooks).

## Tarefas que este guia ajuda a concluir

* [Receber atividade de um endereço de carteira](#connect-wallet-address-activity), criando uma assinatura autenticada, adicionando endereços monitorados e verificando eventos recebidos.
* [Monitorar logs de contratos correspondentes](#event-format), inspecionando eventos `log` dos endereços monitorados e filtrando `address`, `topics` e `data` no receptor.
* [Recuperar entregas interrompidas](#delivery-retries-and-replay), verificando o progresso da assinatura e repetindo correspondências retidas, depois recuperando lacunas fora da janela de replay.

Uma assinatura reúne uma URL HTTPS de recebimento, um segredo de assinatura, endereços EVM monitorados e um objeto `chains` obrigatório. Os endereços se aplicam a todas as redes desse objeto. Use a API com um cabeçalho `x-api-key`; qualquer API key ativa na sua conta pode gerenciar todas as assinaturas dela. [Obtenha uma API key](https://blockvectra.com/en/get-api-key/) antes de começar. A [OpenAPI de Push](https://docs.blockvectra.com/openapi/push.yaml) lista todas as operações e esquemas de Webhook.

## Conectar a atividade de endereços de carteira

1. Implante um receptor que [verifique o corpo original da requisição](#verify-signatures), persista eventos por `id` e confirme o recebimento em até 10 segundos.
2. Consulte `GET /v1/push/chains` e [crie uma assinatura](#create-a-subscription) com sua URL HTTPS e as redes selecionadas. Salve os valores retornados de `id` e `secret`.
3. [Adicione os endereços das carteiras](#add-and-list-addresses). Aguarde `applied_version >= change_version` e registre o `applied_from_block` de cada rede; a correspondência começa nesse bloco.
4. Processe transferências e logs e [recupere lacunas ou blocos substituídos](#delivery-retries-and-replay). Filtre contratos de tokens, destinatários e quantidades inteiras antes de usar notificações no processamento de pagamentos.

## Escolher Webhook, WebSocket ou polling

* **Webhook** envia eventos de endereços monitorados a um receptor HTTPS, com novas tentativas de entrega e replay das correspondências retidas.
* **[WebSocket](https://docs.blockvectra.com/en/guides/websocket-subscriptions/)** transmite `newHeads` e `logs` filtrados por uma conexão persistente. Reconecte, refaça as assinaturas e consulte os blocos perdidos após uma desconexão.
* **[Polling](https://docs.blockvectra.com/en/guides/stablecoin-payments/)** consulta `eth_getLogs` em intervalos limitados de blocos com seu próprio cursor; use-o para monitorar pagamentos ou recuperar logs ausentes.

Verifique `ws` e `subscriptions` em `GET /v1/chains` para saber se há suporte a WebSocket. Se `ws` for false, Webhooks de endereços ainda são uma opção quando a rede aparece na lista autenticada `GET /v1/push/chains`. O suporte a RPC por si só não confirma suporte a Push.

## Capacidade de endereços

O autosserviço oferece até 1,000,000 endereços por assinatura e fica disponível no cadastro. Uma assinatura cobre várias redes com uma única URL de recebimento. A capacidade empresarial oferece 10,000,000 / 100,000,000 endereços por assinatura; [entre em contato para habilitá-la](https://blockvectra.com/en/contact/). Desenvolvedores e agentes de IA têm as mesmas opções de capacidade e preços. Ambos os níveis usam as mesmas tarifas por endereço-dia e evento entregue mostradas em [Preços](https://blockvectra.com/en/pricing/).

## Criar uma assinatura

Consulte `GET /v1/push/chains` para ver as redes disponíveis e suas quantidades mínima, padrão e máxima de confirmações. Um bloco é liberado quando `head - block + 1 >= confirmations`. Cada rede pode usar o padrão enviando `{}`. É necessária pelo menos uma rede; novas redes não entram automaticamente nas assinaturas existentes.

Salve o exemplo abaixo como `create.json`, substituindo a URL pela do seu receptor e selecionando redes da lista de redes. A URL deve usar HTTPS na porta 443, um hostname em vez de um IP literal e não pode conter informações de usuário ou fragmento.

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

Defina `BLOCKVECTRA_API_KEY` no ambiente e execute:

```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
```

Uma criação bem-sucedida retorna HTTP 201 e uma assinatura `online` sem endereços. Armazene o `id` numérico e o `secret` com segurança. O segredo é retornado apenas na criação ou em `POST /subscriptions/{subscription_id}/rotate-secret`; a rotação entra em vigor imediatamente em todas as redes, sem sobreposição. Nenhuma mensagem de teste é enviada.

## Adicionar e listar endereços

Salve um lote de endereços como `addresses.json`, substituindo os endereços de exemplo pelos que você monitora:

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

Defina `SUBSCRIPTION_ID` como o ID da assinatura retornado:

```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"
```

Cada chamada de adição aceita no máximo 10,000 endereços. Os endereços de entrada devem estar em letras minúsculas ou usar maiúsculas e minúsculas EIP-55 válidas; uma entrada inválida rejeita o lote inteiro. Endereços repetidos contam como `unchanged`, portanto é seguro reenviar a mesma requisição de adição. Listas de endereços usam `limit` e `page_token`; `next_page_token: null` marca a última página.

Adicionar endereços retorna `change_version`. Consulte ou inspecione `GET /subscriptions/{subscription_id}` até `applied_version >= change_version`; as alterações normalmente levam cerca de 1 segundo para ser aplicadas. O `applied_from_block` de cada rede identifica o bloco efetivo a partir do qual transações e logs on-chain são associados. Novos endereços não são associados retroativamente.

Criar uma assinatura retorna HTTP 201 para confirmar a criação do recurso de assinatura; HTTP 201 não significa que seu receptor recebeu algum Push por Webhook. A plataforma não envia mensagens de verificação ou teste na criação ou no cadastro de endereços. Para verificar a entrega no receptor, você deve aguardar uma atividade on-chain correspondente nos endereços e redes monitorados.

## Formato dos eventos

Todo POST tem `type: push.events`, `created_at` e `data`. `data` contém `subscription_id`, uma `chain`, `complete_through_block` e `events`. Registre o progresso por rede: um bloco pode ocupar várias mensagens, portanto os números de bloco de eventos individuais não indicam conclusão. Cada mensagem contém no máximo 1,000 eventos, 1 MiB e 50 blocos.

```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"
          }
        ]
      }
    ]
  }
}
```

| Tipo de evento     | O que processar                                                                                                                                                                                                                    |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `native.transfer`  | Transferências nativas de valor bem-sucedidas no nível superior envolvendo um endereço monitorado; `amount` é uma string decimal inteira. Transferências nativas internas são excluídas.                                           |
| `token.transfer`   | Transferências ERC-20, ERC-721 e ERC-1155 envolvendo endereços monitorados; inspecione `standard`, `token`, `token_id`, `amount` e `batch_index`. Transferências em lote ERC-1155 geram um evento por item.                        |
| `log`              | Outros logs que mencionam um endereço monitorado como contrato emissor ou nos topics 1–3; inspecione `address`, `topics`, `data` e `matched`.                                                                                      |
| `subscription.gap` | Um intervalo de `from_block` a `to_block` está indisponível para entrega, com `reason: retention_expired`; recupere-o com a Data API ou `eth_getLogs`.                                                                             |
| `chain.reorg`      | Aviso gratuito de reorg: blocos entregues em `from_block`–`to_block` foram substituídos. Marque ou descarte seus eventos antigos por `ref`, depois mantenha os eventos canônicos reenviados automaticamente e deduplique por `id`. |

Dentro de uma assinatura, deduplique pelo `id` do evento; entre assinaturas, use `ref` e `type`. Ignore campos e tipos de evento desconhecidos. Verifique os fatos on-chain antes de realizar uma ação financeira.

## Verificar assinaturas

Os cabeçalhos são `webhook-id`, `webhook-timestamp`, `webhook-signature` e `bv-subscription-id`. Selecione o segredo apenas entre as assinaturas que você criou; rejeite IDs desconhecidos. Verifique HMAC-SHA256 sobre `webhook-id.webhook-timestamp.raw-body`, usando os bytes do corpo original da requisição, antes de interpretar o JSON. A assinatura é `v1,<base64>`; aceite cerca de cinco minutos de diferença no timestamp e compare em tempo constante.

Esta função Node.js recebe o corpo bruto como `Buffer`, os cabeçalhos da requisição e um mapa de IDs de assinatura para os segredos armazenados:

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

Após a verificação, interprete o corpo, persista o processamento e retorne 2xx em até 10 segundos. O cabeçalho de ID da assinatura não é confiável até que a assinatura seja verificada.

## Verificar seu primeiro evento

Mantenha a assinatura online. Depois que a alteração de endereços for aplicada, aguarde atividade on-chain correspondente e confira se o receptor verifica e armazena o evento de forma durável.

## Parar de monitorar após a verificação

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

Para parar de monitorar endereços, salve os endereços a remover em `addresses.json` e chame `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
```

Cada chamada de remoção aceita no máximo 10,000 endereços. Um endereço que não está sendo monitorado conta como `unchanged`. A chamada retorna `change_version`. Quando `applied_version >= change_version`, os blocos a partir desse bloco efetivo não correspondem mais aos endereços removidos. Eventos já correspondidos (em trânsito, em nova tentativa ou na fila) ainda são entregues; eventos já entregues não são retirados.

Para pausar temporariamente o monitoramento sem excluir configuração ou endereços, defina `status` como `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"}'
```

Uma assinatura `offline` interrompe o monitoramento e a entrega, descarrega os endereços do índice de correspondência e não gera tarifa de endereço em nenhum dia UTC completo em que permaneça offline. Toda a configuração (URL, segredo, endereços, redes e confirmações) é preservada. Aplicar `{"status":"online"}` retoma o monitoramento a partir do bloco efetivo atual e não recupera o período offline.

Use JSON Merge Patch em `PATCH /subscriptions/{subscription_id}` para alterar `url`, `key_id`, `status` ou `chains`: um objeto de rede a adiciona ou atualiza, e `null` a remove. Pelo menos uma rede deve permanecer. Para excluir permanentemente a assinatura:

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

`DELETE` remove permanentemente a assinatura, interrompe imediatamente a entrega em todas as redes e destrói o segredo e os endereços.

## Entrega, novas tentativas e replay

A entrega ocorre pelo menos uma vez. Cada rede de uma assinatura é ordenada por bloco e posição no bloco; lotes com falha bloqueiam eventos posteriores nessa rede. Redes diferentes têm progresso independente e podem fazer POST simultaneamente. Uma nova tentativa de um lote idêntico mantém `webhook-id`, mas um lote alterado pode ter um novo ID: deduplique eventos, não lotes.

Qualquer 2xx em até 10 segundos confirma processamento durável. Redirecionamentos não são seguidos; 3xx e 410 são falhas. Após uma falha, as tentativas ocorrem imediatamente, depois em intervalos de 5 segundos, 30 segundos, 2 minutos, 10 minutos, 30 minutos e 1 hora, depois a cada hora. Um 429 `Retry-After` pode ampliar a espera até uma hora. Inspecione `condition`, `last_error` e `next_attempt_at` de cada rede quando a entrega parar. As condições são `receiver_failing`, `insufficient_balance` e `key_revoked`; a última exige alterar `key_id` para outra API key ativa da conta.

Eventos não entregues expiram fora da janela de retenção e geram `subscription.gap`. `POST /subscriptions/{subscription_id}/replay` recebe `chain` e `from_block`; consulte `replayable_from_block` em `GET /push/chains` e o progresso da assinatura. Replay entrega correspondências existentes e não recupera eventos anteriores à adição de um endereço ou rede.

`chain.reorg` informa que blocos já entregues foram substituídos; não indica uma lacuna de entrega. Reorgs menos profundas que sua quantidade de confirmações são invisíveis. Para reorgs que afetem blocos entregues com profundidade de até 1,024 blocos, eventos canônicos são reenviados automaticamente com novos `id`s. Marque ou descarte eventos substituídos por `ref`, mantenha os eventos canônicos e deduplique por `id`; para registros de pagamentos, reconcilie por `ref` e `tx_hash`. Uma reorg mais profunda interrompe a rede: verifique `halted` em `GET /push/chains`; o reenvio canônico ocorre após a restauração da rede. O evento de controle não avança `complete_through_block`.

Consulte eventos de dados entregues com `GET /subscriptions/{subscription_id}/events?chain=...`, adicionando opcionalmente `from_block`, `to_block`, `limit` e `page_token`. As linhas de histórico contêm `event`, `replay_epoch`, `orphaned` e `delivered_at`; `orphaned: true` marca um bloco posteriormente substituído. A admissão de consultas de histórico pode retornar 402 `insufficient_balance` (`data.reason`: `balance_exhausted` ou `free_grant_exhausted`), 403 `key_cap_exhausted` (`data.cu_cap`) ou 429 `rate_limited` (`key_rate_limit` ou `free_plan_call_limit`). Um 429 `cost_exceeds_burst` tem motivo `request_exceeds_burst` e `data.max`: aumente a capacidade de burst antes de tentar novamente. Consulte [tratamento de erros](https://docs.blockvectra.com/en/errors/) para intervalos inválidos e orientações de novas tentativas.

## Cobrança e exemplo

Os pesos vêm de `GET /v1/plans`. Eventos de dados entregues, requisições de histórico bem-sucedidas e endereços-dia têm pesos separados; chamadas de gestão além do histórico, eventos de controle, entregas com falha e tentativas automáticas são gratuitos. Cada evento entregue é cobrado uma vez; replay solicitado pelo cliente e reenvio de eventos canônicos geram novas cobranças de entrega.

A cobrança de endereços usa a maior quantidade de endereços de cada assinatura durante sua parte online do dia UTC, após a franquia gratuita de endereços da conta, compartilhada entre assinaturas (as mais antigas primeiro). O mesmo endereço em duas assinaturas conta duas vezes; adicionar redes altera a cobrança de eventos, não a de endereços. Uma assinatura offline durante todo o dia UTC não tem cobrança de endereços.

| Uso | Unidade de cobrança | CU |
| --- | --- | --- |
| `push.address_day` | Endereço-dia cobrado | 33 |
| `push.history` | Requisição de histórico bem-sucedida | 25 |
| `push.log` | Evento de dados entregue | 150 |
| `push.native_transfer` | Evento de dados entregue | 150 |
| `push.token_transfer` | Evento de dados entregue | 150 |

Endereços gratuitos por conta por dia UTC: 1000

Franquia gratuita de endereços por conta por dia UTC, compartilhada entre todos os grupos de assinatura, independentemente do plano. Para cada grupo, conte sua maior quantidade de endereços enquanto esteve online naquele dia; distribua a franquia em ordem crescente de ID de grupo. O mesmo endereço em dois grupos conta duas vezes; a quantidade de redes em um grupo não multiplica sua quantidade de endereços. Um grupo offline ou excluído durante o dia inteiro não contribui. Para cada grupo, a quantidade restante após sua parcela da franquia é multiplicada pelo peso de CU de `push.address_day` em `method_weights`. A franquia configurada atual vem da mesma política de preços usada na cobrança de endereços-dia; não é um limite de capacidade da conta nem uma franquia separada por grupo.

Exemplo: 10 eventos native.transfer entregues, 2 requisições de histórico bem-sucedidas e 10 endereços-dia cobrados custam 10 × 150 + 2 × 25 + 10 × 33 = 1880 CU. Endereços-dia cobrados são contados após a franquia gratuita de endereços da conta.

Consulte as [regras de cobrança](https://docs.blockvectra.com/en/guides/billing-rules/) e a [página de preços](https://blockvectra.com/en/pricing/) para medição em CU e conversão.

## Recursos relacionados

* Compare eventos compatíveis, cobertura de redes e preços na [visão geral da API de Webhooks de blockchain](https://blockvectra.com/en/webhooks/).
