Configurar Webhooks de blockchain: assinaturas, deduplicação e replay
Crie assinaturas de endereços via HTTP, verifique assinaturas no corpo bruto, deduplique IDs de eventos e recupere correspondências retidas ou blocos ausentes.
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.
Tarefas que este guia ajuda a concluir
- Receber atividade de um endereço de carteira, criando uma assinatura autenticada, adicionando endereços monitorados e verificando eventos recebidos.
- Monitorar logs de contratos correspondentes, inspecionando eventos
logdos endereços monitorados e filtrandoaddress,topicsedatano receptor. - Recuperar entregas interrompidas, 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 antes de começar. A OpenAPI de Push lista todas as operações e esquemas de Webhook.
Conectar a atividade de endereços de carteira
- Implante um receptor que verifique o corpo original da requisição, persista eventos por
ide confirme o recebimento em até 10 segundos. - Consulte
GET /v1/push/chainse crie uma assinatura com sua URL HTTPS e as redes selecionadas. Salve os valores retornados deidesecret. - Adicione os endereços das carteiras. Aguarde
applied_version >= change_versione registre oapplied_from_blockde cada rede; a correspondência começa nesse bloco. - Processe transferências e logs e recupere lacunas ou blocos substituídos. 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 transmite
newHeadselogsfiltrados por uma conexão persistente. Reconecte, refaça as assinaturas e consulte os blocos perdidos após uma desconexão. - Polling consulta
eth_getLogsem 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. 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.
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.
{
"url": "https://hooks.example.com/push",
"chains": {
"bsc_mainnet": {
"confirmations": 1
},
"base_mainnet": {}
}
}Defina BLOCKVECTRA_API_KEY no ambiente e execute:
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.jsonUma 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:
{
"addresses": [
"0x9725db73f2cd8657f3e1841e5689f210ee54a92d",
"0x99d47bB552ae095159C251836De6A5d524076872"
]
}Defina SUBSCRIPTION_ID como o ID da assinatura retornado:
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.
{
"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:
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
Para parar de monitorar endereços, salve os endereços a remover em addresses.json e chame POST /subscriptions/{subscription_id}/addresses/remove:
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.jsonCada 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:
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:
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 ids. 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 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 e a página de preços 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.
Última atualização: