Conceptos
Webhooks
Los webhooks te avisan en el momento exacto en que algo pasa on-chain, sin polling. Registra tu endpoint en el Portal de Comercios, guarda el secret whsec_ y verifica la firma HMAC-SHA256 de cada entrega antes de procesarla.
Cómo funciona el flujo
Detectamos el evento en la red, generamos el payload, lo firmamos y lo entregamos a tu endpoint. Si tu servidor no responde 2xx, reintentamos con backoff exponencial.
- 1
Transacción detectada on-chain
El motor de Vaultcore observa las redes soportadas (Polygon, Base, Tron y más) y detecta depósitos y confirmaciones de transferencias.
- 2
Payload firmado con HMAC-SHA256
Se genera el cuerpo JSON del evento y se firma con el secret de tu webhook. La firma viaja en el header X-Vaultcore-Signature.
- 3
POST a tu endpoint HTTPS
Enviamos un POST a la URL registrada. Verifica la firma sobre el raw body y responde 200 OK en menos de 5 segundos.
- 4
Reintento automático ante fallas
Si respondes 5xx o hay timeout, programamos reintentos asíncronos con backoff exponencial y jitter hasta 4 veces.
Seguridad: firma HMAC-SHA256
Cada endpoint registrado recibe un Webhook Secret único con prefijo whsec_ (por ejemplo whsec_e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0). Firmamos el raw body exacto con ese secret y enviamos el resultado hexadecimal en X-Vaultcore-Signature.
Signature = HMAC_SHA256(key = webhook.secret, data = raw_body)Cambios en el orden de las propiedades o espacios introducidos por un parser JSON alteran el hash y romperán la verificación. Usa siempre el buffer del raw body y compara con una función de tiempo constante (crypto.timingSafeEqual, hmac.compare_digest, hash_equals).
Headers de cada entrega
Toda solicitud POST enviada a tu URL incluye estos encabezados.
Headers HTTP
Content-TypestringSiempre application/json.
X-Vaultcore-SignaturestringrequeridoFirma hexadecimal HMAC-SHA256 del raw body (64 caracteres).
X-Vaultcore-EventstringTipo de evento notificado, por ejemplo deposit.confirmed.
X-Vaultcore-Delivery-IdstringIdentificador único del intento de entrega (del_01J2K3L4M5N6P7Q8R9S0). Útil para deduplicar.
X-Vaultcore-SimulationstringPresente solo en eventos de prueba o simulación enviados desde el portal.
User-Agentstringvaultcore-webhook-dispatcher/1.0
POST https://tu-dominio.com/webhooks/vaultcore
Content-Type: application/json
X-Vaultcore-Signature: a1b2c3d4e5f6... # 64 caracteres hex
X-Vaultcore-Event: deposit.confirmed
X-Vaultcore-Delivery-Id: del_01J2K3L4M5N6P7Q8R9S0
User-Agent: vaultcore-webhook-dispatcher/1.0Eventos soportados
Suscríbete solo a los eventos que tu backend necesita procesar.
Eventos
deposit.detectedeventDepósito entrante detectado en mempool o bloque reciente (0 a N-1 confirmaciones). Muestra "depósito en camino", pero no acredites saldo disponible todavía.
deposit.confirmedeventEl depósito alcanzó las confirmaciones requeridas y fue acreditado en el libro contable. Este es el evento definitivo para acreditar al usuario o entregar bienes y servicios.
transfer.confirmedeventRetiro o transferencia saliente confirmada en la blockchain con su tx_hash final. Marca la orden como completada.
transfer.failedeventLa transferencia falló o fue cancelada; los fondos se reintegraron al balance contable. Notifica al usuario o reintenta con nuevos parámetros.
gas_tank.low_balanceeventEl balance de la wallet de gas está por debajo del umbral operativo. Recarga fondos nativos (POL, ETH, TRX) para no bloquear barridos ni retiros.
Ejemplos de payload
Todos los eventos comparten la misma envoltura: id, event, created_at y data.
{
"id": "evt_01J9X1A2B3C4D5E6F7G8H9J0",
"event": "deposit.detected",
"created_at": "2026-08-20T21:15:30.120Z",
"data": {
"deposit_id": "dep_01J9X1A2B3C4D5E6F7G8H9J0",
"merchant_id": "mrc_01HZX89ABCDEF1234567890",
"end_user_id": "usr_internal_9381",
"network": "POLYGON",
"asset": "USDT",
"amount": "250.000000",
"tx_hash": "0x4f8a9b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a",
"deposit_address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"confirmations": 2,
"required_confirmations": 12,
"detected_at": "2026-08-20T21:15:30.000Z"
}
}Idempotencia y deduplicación
Por la naturaleza de las redes distribuidas y los reintentos automáticos, tu receptor debe ser idempotente.
- Guarda los eventos procesados: almacena data.deposit_id, data.transfer_id o X-Vaultcore-Delivery-Id con una restricción UNIQUE.
- Ignora duplicados: si el identificador ya fue procesado, responde 200 OK sin volver a acreditar fondos ni duplicar acciones.
- Responde rápido: devuelve un 2xx en menos de 5 segundos y mueve el trabajo pesado (emails, sincronizaciones) a workers o colas.
- Los eventos pueden llegar desordenados: no asumas que deposit.detected llega siempre antes que deposit.confirmed.
Reintentos y resiliencia
Si respondes fuera del rango 200-299 (500, 502, 504) o la conexión supera 10 segundos de timeout, reintentamos hasta 4 veces con backoff exponencial y jitter.
Calendario de reintentos
1er reintento~60 sAproximadamente 1 minuto después del fallo.
2do reintento~5 min300 segundos después del intento anterior.
3er reintento~15 min900 segundos después del intento anterior.
4to reintento~1 h3600 segundos después; es el intento final.
EXHAUSTEDstatusSi tras 4 intentos sigue fallando, la entrega queda en EXHAUSTED. Puedes inspeccionar el error y hacer un re-envío manual desde el Portal de Comercios.
Mantén además un job de conciliación sobre GET /v1/deposits cada pocos minutos: webhooks y conciliación se complementan.
Verificar la firma en tu servidor
Implementaciones listas para copiar en Node.js, Python, Go y PHP.
import express, { Request, Response } from "express";
import crypto from "crypto";
const app = express();
const WEBHOOK_SECRET = process.env.VAULTCORE_WEBHOOK_SECRET || "whsec_...";
// IMPORTANTE: capturar el raw body como Buffer para verificar la firma
app.post(
"/webhooks/vaultcore",
express.raw({ type: "application/json" }),
(req: Request, res: Response) => {
const signatureHeader = req.headers["x-vaultcore-signature"] as string;
const eventType = req.headers["x-vaultcore-event"] as string;
const deliveryId = req.headers["x-vaultcore-delivery-id"] as string;
if (!signatureHeader) {
return res.status(401).json({ error: "Missing X-Vaultcore-Signature header" });
}
const rawBody = req.body; // Buffer en crudo
const computedSignature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const signatureBuffer = Buffer.from(signatureHeader, "utf8");
const computedBuffer = Buffer.from(computedSignature, "utf8");
if (
signatureBuffer.length !== computedBuffer.length ||
!crypto.timingSafeEqual(signatureBuffer, computedBuffer)
) {
return res.status(401).json({ error: "Invalid webhook signature" });
}
const payload = JSON.parse(rawBody.toString("utf8"));
console.log(`Webhook verificado [${eventType}] - Delivery ID: ${deliveryId}`);
switch (eventType) {
case "deposit.confirmed":
// Acredita saldo de forma idempotente usando payload.data.deposit_id
break;
case "transfer.confirmed":
// Marca el retiro como completado con payload.data.tx_hash
break;
default:
break;
}
return res.status(200).json({ received: true });
},
);
app.listen(3000);Configuración y monitoreo en el portal
La gestión de webhooks no requiere integración por API: se administra de forma visual desde el Portal de Comercios.
- 1
Configura tus endpoints
Entra a Desarrolladores > Webhooks, haz clic en Crear Webhook, indica tu URL HTTPS receptora y selecciona los eventos a los que quieres suscribirte.
- 2
Guarda el secret whsec_
Al crear el webhook se genera tu Webhook Secret. Guárdalo en el gestor de secretos de tu servidor para verificar X-Vaultcore-Signature.
- 3
Simula eventos en tiempo real
Usa Probar / Simular Webhook para enviar eventos de prueba instantáneos y validar tu lógica de verificación antes de ir a producción.
- 4
Revisa el historial de entregas
El visor muestra headers enviados, código HTTP recibido, cuerpo devuelto y latencia en milisegundos. Puedes presionar Re-enviar sobre cualquier entrega fallida.
Rota el secret desde el Portal de Comercios si sospechas que se filtró; durante la rotación acepta temporalmente la firma antigua y la nueva.