Configurar Webhooks de blockchain: firmas, deduplicación y reproducción
Cree suscripciones de direcciones a través de HTTP, verifique firmas en el cuerpo sin procesar, deduplique IDs de eventos y recupere coincidencias retenidas o bloques faltantes.
Monitoree una dirección de billetera EVM y reciba sus transferencias nativas, transferencias de tokens y logs de contratos coincidentes en su endpoint HTTPS para notificaciones de actividad de billetera o monitoreo de eventos de contratos inteligentes. Los desarrolladores y agentes de IA utilizan la misma API de suscripción HTTP. Para notificaciones de pago ERC-20 en USDT / USDC, siga el receptor de pagos con stablecoins.
Tareas que esta guía le ayuda a completar
- Recibir actividad de direcciones de billetera creando una suscripción autenticada, agregando direcciones vigiladas y verificando los eventos entrantes.
- Monitorear logs de contratos coincidentes inspeccionando eventos
logpara direcciones vigiladas y filtrandoaddress,topicsydataen su receptor. - Recuperar entregas interrumpidas verificando el progreso de la suscripción y reproduciendo coincidencias retenidas, luego recuperando lagunas fuera de la ventana de reproducción.
Una suscripción combina una URL de recepción HTTPS, un secreto de firma, direcciones EVM vigiladas y un objeto chains obligatorio. Las direcciones se aplican a cada cadena en ese objeto. Utilice la API con un encabezado x-api-key; cualquier clave activa en su cuenta puede gestionar todas sus suscripciones. Obtenga una API key antes de comenzar. La OpenAPI de Push enumera cada operación y esquema de webhook.
Conectar la actividad de direcciones de billetera
- Despliegue un receptor que verifique el cuerpo original de la solicitud, persista los eventos por
idy confirme su recepción en un plazo de 10 segundos. - Consulte
GET /v1/push/chains, luego cree una suscripción con su URL HTTPS y las cadenas seleccionadas. Guarde elidy elsecretdevueltos. - Agregue las direcciones de billetera. Espere a que
applied_version >= change_versiony registre elapplied_from_blockde cada cadena; la coincidencia comienza allí. - Procese transferencias y logs, y recupere lagunas o bloques reemplazados. Filtre contratos de tokens, destinatarios y montos enteros antes de utilizar las notificaciones en el procesamiento de pagos.
Elegir Webhook, WebSocket o polling
- Webhook envía eventos de direcciones vigiladas a un receptor HTTPS, con reintentos de entrega y reproducción de coincidencias retenidas.
- WebSocket transmite
newHeadsylogsfiltrados a través de una conexión persistente. Reconecte, vuelva a suscribirse y consulte los bloques perdidos tras una desconexión. - Polling consulta
eth_getLogsen rangos de bloques delimitados con su propio cursor; utilícelo para monitorear pagos o recuperar logs faltantes.
Verifique ws y subscriptions en GET /v1/chains para comprobar la compatibilidad con WebSocket. Si ws es false, los Webhooks de direcciones siguen siendo una opción cuando esa cadena aparece en la lista autenticada de GET /v1/push/chains. La compatibilidad con RPC por sí sola no establece la compatibilidad con Push.
Capacidad de direcciones
El autoservicio admite hasta 1,000,000 de direcciones por suscripción y está disponible al registrarse. Una suscripción cubre múltiples cadenas con una sola URL de recepción. La capacidad empresarial admite 10,000,000 / 100,000,000 de direcciones por suscripción; contáctenos para habilitarla. Los desarrolladores y los agentes de IA tienen las mismas opciones de capacidad y precios. Ambos niveles utilizan las mismas tarifas por dirección-día y por evento entregado que se muestran en precios.
Crear una suscripción
Consulte GET /v1/push/chains para ver las cadenas disponibles y sus recuentos mínimo, predeterminado y máximo de confirmaciones. Un bloque se libera cuando head - block + 1 >= confirmations. Cada cadena puede usar su valor predeterminado proporcionando {}. Se requiere al menos una cadena; las cadenas nuevas no se unen automáticamente a las suscripciones existentes.
Guarde el siguiente ejemplo como create.json, reemplazando la URL con su receptor y seleccionando cadenas de la lista de cadenas. La URL debe usar HTTPS en el puerto 443, un nombre de host en lugar de una IP literal y no contener información de usuario ni fragmentos.
{
"url": "https://hooks.example.com/push",
"chains": {
"bsc_mainnet": {
"confirmations": 1
},
"base_mainnet": {}
}
}Establezca BLOCKVECTRA_API_KEY en su entorno y luego ejecute:
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.jsonUna creación exitosa devuelve HTTP 201 y una suscripción online sin direcciones. Guarde su id numérico y su secret de forma segura. El secreto se devuelve solo al momento de la creación o en POST /subscriptions/{subscription_id}/rotate-secret; la rotación entra en vigor inmediatamente en todas las cadenas, sin solapamiento. No se envía ningún mensaje de prueba.
Agregar y listar direcciones
Guarde un lote de direcciones como addresses.json, reemplazando las direcciones de ejemplo con las que vigila:
{
"addresses": [
"0x9725db73f2cd8657f3e1841e5689f210ee54a92d",
"0x99d47bB552ae095159C251836De6A5d524076872"
]
}Establezca SUBSCRIPTION_ID con el ID de suscripción devuelto:
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 llamada de adición acepta un máximo de 10,000 direcciones. Las direcciones de entrada deben ser en minúsculas o con mayúsculas y minúsculas válidas según EIP-55; una entrada no válida rechaza todo el lote. Las direcciones repetidas cuentan como unchanged, por lo que es seguro reenviar la misma solicitud de adición. Las listas de direcciones usan limit y page_token; next_page_token: null marca la última página.
Al agregar direcciones se devuelve change_version. Realice sondeos o inspeccione GET /subscriptions/{subscription_id} hasta que applied_version >= change_version; los cambios normalmente tardan aproximadamente 1 segundo en aplicarse. El applied_from_block de cada cadena identifica el bloque efectivo a partir del cual se emparejan las transacciones y los logs on-chain. Las direcciones nuevas no se emparejan retroactivamente.
La creación de una suscripción devuelve HTTP 201 para confirmar que se creó el recurso de suscripción; HTTP 201 no significa que su receptor haya recibido ningún push de webhook. La plataforma no envía mensajes de verificación ni de prueba al momento de la creación o del registro de direcciones. Debe esperar a que ocurra actividad on-chain coincidente en las direcciones y cadenas vigiladas para verificar la entrega en su receptor.
Formato de los eventos
Cada POST incluye type: push.events, created_at y data. data contiene subscription_id, una chain, complete_through_block y events. Registre el progreso por cadena: un bloque puede abarcar varios mensajes, por lo que los números de bloque de eventos individuales no son un marcador de finalización. Cada mensaje contiene como máximo 1,000 eventos, 1 MiB y 50 bloques.
{
"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 | Qué gestionar |
|---|---|
native.transfer | Transferencias exitosas de valor nativo de nivel superior que involucran una dirección vigilada; amount es una cadena decimal entera. Se excluyen las transferencias nativas internas. |
token.transfer | Transferencias ERC-20, ERC-721 y ERC-1155 que involucran direcciones vigiladas; inspeccione standard, token, token_id, amount y batch_index. Las transferencias por lotes ERC-1155 generan un evento por elemento. |
log | Otros logs que identifican una dirección vigilada como contrato emisor o en los topics 1–3; inspeccione address, topics, data y matched. |
subscription.gap | Un rango desde from_block hasta to_block no está disponible para entrega, con reason: retention_expired; recupere con Data API o eth_getLogs. |
chain.reorg | Aviso de reorganización gratuito: los bloques entregados en from_block–to_block fueron reemplazados. Marque o descarte sus eventos antiguos por ref, conserve los eventos canónicos reenviados automáticamente y deduplique por id. |
Dentro de una suscripción, deduplique por el id del evento; entre suscripciones use ref y type. Ignore los campos y tipos de evento desconocidos. Verifique los hechos on-chain antes de realizar cualquier acción financiera.
Verificar firmas
Los encabezados son webhook-id, webhook-timestamp, webhook-signature y bv-subscription-id. Elija el secreto únicamente de las suscripciones que haya creado; rechace los IDs desconocidos. Verifique HMAC-SHA256 sobre webhook-id.webhook-timestamp.raw-body, utilizando los bytes del cuerpo original de la solicitud, antes de analizar el JSON. La firma es v1,<base64>; permita unos cinco minutos de desfase en la marca temporal y compare en tiempo constante.
Esta función de Node.js acepta el cuerpo sin procesar como un Buffer, los encabezados de la solicitud y un mapa de IDs de suscripción a secretos almacenados:
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);
}Después de la verificación, analice el cuerpo, persista el procesamiento y devuelva 2xx en un plazo de 10 segundos. El encabezado del ID de suscripción no es confiable hasta que se verifique la firma.
Verificar su primer evento
Mantenga la suscripción online. Después de que se aplique el cambio de dirección, espere a que haya actividad on-chain coincidente y verifique que su receptor compruebe y almacene el evento de forma duradera.
Detener la escucha tras la verificación
Para dejar de vigilar direcciones, guarde las direcciones a eliminar en addresses.json y llame a 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 llamada de eliminación acepta un máximo de 10,000 direcciones. Una dirección que no se esté vigilando actualmente cuenta como unchanged. La llamada devuelve change_version. Una vez que applied_version >= change_version, los bloques a partir de ese bloque efectivo ya no coincidirán con las direcciones eliminadas. Los eventos previamente emparejados (en tránsito, reintentando o en cola) aún se entregan; los eventos ya entregados no se retiran.
Para pausar temporalmente la escucha sin eliminar la configuración ni las direcciones, establezca status en 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"}'Una suscripción offline detiene la escucha y la entrega, descarga las direcciones del índice de coincidencia y no incurre en tarifas por dirección durante ningún día UTC completo en que permanezca offline. Toda la configuración (URL, secreto, direcciones, cadenas y confirmaciones) se conserva. Aplicar {"status":"online"} reanuda la escucha desde el bloque efectivo actual y no recupera el período offline.
Utilice JSON Merge Patch en PATCH /subscriptions/{subscription_id} para cambiar url, key_id, status o chains: un objeto de cadena lo agrega o actualiza, y null lo elimina. Debe permanecer al menos una cadena. Para eliminar permanentemente la suscripción:
curl --fail-with-body -sS -X DELETE "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"DELETE elimina permanentemente la suscripción, detiene la entrega en todas las cadenas de inmediato y destruye el secreto y las direcciones.
Entrega, reintentos y reproducción
La entrega es al menos una vez. La cadena de cada suscripción se ordena por bloque y posición en el bloque; los lotes fallidos bloquean los eventos posteriores en esa cadena. Diferentes cadenas tienen un progreso independiente y pueden realizar llamadas POST concurrentemente. Un reintento de un lote idéntico conserva webhook-id, pero un lote modificado puede tener un nuevo ID: deduplique eventos, no lotes.
Cualquier 2xx dentro de los 10 segundos confirma el procesamiento duradero. No se siguen las redirecciones; 3xx y 410 se consideran fallos. Después de un fallo, los reintentos siguen intervalos de inmediato, 5 segundos, 30 segundos, 2 minutos, 10 minutos, 30 minutos y 1 hora, y luego cada hora. Un encabezado Retry-After con 429 puede extender la espera hasta por una hora. Inspeccione condition, last_error y next_attempt_at de cada cadena cuando la entrega se detenga. Las condiciones son receiver_failing, insufficient_balance y key_revoked; esta última requiere actualizar key_id a otra clave activa de la cuenta.
Los eventos no entregados vencen fuera de la ventana de retención y producen subscription.gap. POST /subscriptions/{subscription_id}/replay toma chain y from_block; consulte replayable_from_block en GET /push/chains y el progreso de la suscripción. La reproducción entrega coincidencias existentes y no puede recuperar eventos anteriores a la adición de una dirección o cadena.
chain.reorg le notifica que los bloques ya entregados fueron reemplazados; no indica una brecha de entrega. Las reorganizaciones menos profundas que su recuento de confirmaciones son invisibles. Para reorganizaciones que afectan a bloques entregados de hasta 1,024 bloques de profundidad, los eventos canónicos se vuelven a entregar automáticamente con nuevos id. Marque o descarte los eventos reemplazados por ref, conserve los eventos canónicos y deduplique por id; para registros de pago, reconcilie por ref y tx_hash. Una reorganización más profunda detiene la cadena: verifique halted en GET /push/chains; la reentrega canónica sigue después de que se restaure la cadena. El evento de control no hace avanzar complete_through_block.
Consulte los eventos de datos entregados con GET /subscriptions/{subscription_id}/events?chain=..., agregando opcionalmente from_block, to_block, limit y page_token. Las filas de historial contienen event, replay_epoch, orphaned y delivered_at; orphaned: true marca un bloque reemplazado posteriormente. La admisión al historial puede devolver 402 insufficient_balance (data.reason: balance_exhausted o free_grant_exhausted), 403 key_cap_exhausted (data.cu_cap) o 429 rate_limited (key_rate_limit o free_plan_call_limit). Un 429 cost_exceeds_burst tiene el motivo request_exceeds_burst y data.max: aumente la capacidad de ráfaga antes de reintentar. Consulte el manejo de errores para rangos inválidos y orientación sobre reintentos.
Facturación y ejemplo
Los pesos provienen de GET /v1/plans. Los eventos de datos entregados, las solicitudes de historial exitosas y las direcciones-día tienen pesos separados; las llamadas de gestión distintas del historial, los eventos de control, las entregas fallidas y los reintentos automáticos son gratuitos. Cada evento entregado se cobra una vez; la reproducción solicitada por el cliente y la reentrega de eventos canónicos incurren en nuevos cargos de entrega.
La facturación de direcciones utiliza el mayor recuento de direcciones de cada suscripción durante su período online del día UTC, después de la franquicia gratuita de direcciones de la cuenta compartida entre suscripciones (las suscripciones más antiguas primero). La misma dirección en dos suscripciones cuenta dos veces; agregar cadenas cambia los cargos por eventos, no los cargos por dirección. Una suscripción offline durante todo el día UTC no tiene cargo por dirección.
| Uso | Unidad de facturación | CU |
|---|---|---|
push.address_day | Dirección-día facturable | 33 |
push.history | Solicitud de historial exitosa | 25 |
push.log | Evento de datos entregado | 150 |
push.native_transfer | Evento de datos entregado | 150 |
push.token_transfer | Evento de datos entregado | 150 |
Direcciones gratuitas por cuenta por día UTC: 1000
Franquicia de direcciones gratuitas por cuenta por día UTC, compartida entre todos los grupos de suscripción independientemente del plan. Para cada grupo, se cuenta la cantidad máxima de direcciones mientras estuvo online durante ese día; la franquicia se asigna en orden ascendente de ID de grupo. La misma dirección en dos grupos cuenta dos veces; el número de cadenas en un grupo no multiplica su recuento de direcciones. Un grupo offline o eliminado durante todo el día no aporta nada. Para cada grupo, la cantidad restante tras su parte de la franquicia se multiplica por el peso en CU de `push.address_day` en `method_weights`. La franquicia actualmente configurada procede de la misma política de precios utilizada para el cobro de dirección-día; no es un límite de capacidad de la cuenta ni una franquicia independiente por grupo.
Ejemplo: 10 eventos native.transfer entregados, 2 solicitudes de historial exitosas y 10 direcciones-día facturables cuestan 10 × 150 + 2 × 25 + 10 × 33 = 1880 CU. Las direcciones-día facturables se contabilizan tras la franquicia de direcciones gratuitas de la cuenta.
Consulte las reglas de facturación y la página de precios para la medición y conversión de CU.
Recursos relacionados
- Compare eventos compatibles, cobertura de cadenas y precios en la descripción general de la API de Webhooks de blockchain.
Última actualización: