#Polling no escala. Los webhooks sí.
El patrón más común para saber si algo cambió en un sistema externo es hacer polling: preguntar cada X segundos “¿hay algo nuevo?”. Funciona, pero tiene problemas reales:
- Latencia: tus datos están desactualizados hasta el próximo poll
- Coste: miles de peticiones vacías que devuelven “nada nuevo”
- Rate limits: cuantas más peticiones, más cerca del límite
Los webhooks invierten el modelo: BeeL te avisa a ti cuando algo ocurre. Factura emitida, factura pagada, estado VeriFactu actualizado — tu servidor recibe un evento en tiempo real.
La API de BeeL soporta webhooks con verificación HMAC-SHA256 incluida en el SDK de TypeScript. Seguridad enterprise sin implementar criptografía.
#Cómo funcionan los webhooks de BeeL
- Configuras una URL de tu servidor (endpoint) en el panel de BeeL
- BeeL te asigna un webhook secret (
whsec_...) - Cuando ocurre un evento, BeeL envía un POST a tu URL con:
- Body: JSON con los datos del evento
- Header
BeeL-Signature: firma HMAC-SHA256 para verificar autenticidad
#Tipos de eventos
| Evento | Cuándo se dispara |
|---|---|
invoice.created | Se crea un borrador de factura |
invoice.issued | Se emite una factura (número asignado + VeriFactu) |
invoice.paid | Se marca una factura como pagada |
invoice.sent | Se envía una factura por email |
invoice.voided | Se anula una factura emitida |
verifactu.status_updated | Cambia el estado de registro VeriFactu |
El evento verifactu.status_updated es especialmente útil: te permite saber cuándo la AEAT ha procesado y aceptado (o rechazado) el registro de una factura.
#Implementación con Express.js y el SDK
import express from 'express';
import { WebhookVerifier } from '@beel_es/sdk';
const app = express();
const verifier = new WebhookVerifier(process.env.BEEL_WEBHOOK_SECRET!);
// IMPORTANTE: express.raw() para recibir el body sin parsear
app.post('/webhooks/beel', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = verifier.verify(
req.body,
req.headers['beel-signature'] as string
);
switch (event.type) {
case 'invoice.issued':
console.log(`Factura emitida: ${event.data.invoice_number}`);
// Actualizar tu sistema: CRM, contabilidad, notificaciones...
break;
case 'invoice.paid':
console.log(`Factura pagada: ${event.data.invoice_number}`);
// Marcar como cobrada en tu sistema
break;
case 'verifactu.status_updated':
console.log(`VeriFactu: ${event.data.status}`);
// Registrar el estado de cumplimiento
break;
default:
console.log(`Evento no gestionado: ${event.type}`);
}
res.json({ received: true });
} catch (err) {
console.error('Webhook inválido:', err);
res.status(400).json({ error: 'Invalid signature' });
}
});
app.listen(3000);⚠️Body sin parsear
El verificador necesita el body raw (sin parsear por JSON middleware). Si usas express.json() globalmente, excluye la ruta del webhook o usa express.raw() específicamente.
#Cómo funciona la verificación HMAC-SHA256
La verificación protege contra dos amenazas:
- Suplantación: alguien envía eventos falsos a tu endpoint
- Replay attacks: alguien reenvía un evento legítimo antiguo
#El proceso
El header BeeL-Signature tiene el formato:
BeeL-Signature: t=1712678400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdt= timestamp Unix (segundos) del momento del envíov1= firma HMAC-SHA256
El SDK verifica:
- Parsear el header para extraer timestamp y firma
- Calcular
HMAC-SHA256(secret, timestamp + "." + body) - Comparar la firma calculada con
v1usando comparación en tiempo constante (previene timing attacks) - Verificar frescura: el timestamp no debe tener más de 5 minutos de antigüedad (configurable)
// Internamente, el SDK hace esto:
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
// Comparación en tiempo constante — NO usar ===
crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);ℹ️¿Por qué comparación en tiempo constante?
Si usas ===, el tiempo de respuesta varía según cuántos caracteres coinciden. Un atacante puede inferir la firma correcta carácter a carácter midiendo tiempos de respuesta. timingSafeEqual tarda lo mismo independientemente de cuántos caracteres coincidan.
#Verificación manual (sin SDK)
Si no usas Node.js, puedes implementar la verificación en cualquier lenguaje:
# Python
import hmac
import hashlib
import time
def verify_webhook(body: bytes, signature_header: str, secret: str):
# 1. Parsear header
parts = dict(p.split('=', 1) for p in signature_header.split(','))
timestamp = parts['t']
signature = parts['v1']
# 2. Verificar frescura (5 minutos)
if abs(time.time() - int(timestamp)) > 300:
raise ValueError('Timestamp too old')
# 3. Calcular firma esperada
payload = f"{timestamp}.{body.decode()}"
expected = hmac.new(
secret.encode(), payload.encode(), hashlib.sha256
).hexdigest()
# 4. Comparar en tiempo constante
if not hmac.compare_digest(expected, signature):
raise ValueError('Invalid signature')#Reintentos y manejo de fallos
Si tu servidor no responde (timeout o error 5xx), BeeL reintenta con backoff exponencial:
| Intento | Delay |
|---|---|
| 1 | Inmediato |
| 2 | 1 minuto |
| 3 | 5 minutos |
| 4 | 30 minutos |
| 5 | 2 horas |
Puedes ver los logs de entrega en tu panel de BeeL para diagnosticar problemas.
Buenas prácticas:
- Responde con
200lo antes posible — procesa el evento de forma asíncrona si es pesado - Implementa idempotencia: guarda el
event.idy descarta duplicados - No confíes en el orden de llegada — los eventos pueden llegar desordenados
#Ejemplo: sincronizar con un sistema de contabilidad
// Webhook handler que sincroniza facturas pagadas con contabilidad
case 'invoice.paid':
const invoice = event.data;
// Registrar en el sistema contable
await accounting.createEntry({
date: invoice.payment_date,
description: `Cobro factura ${invoice.invoice_number}`,
debit: { account: '570', amount: invoice.total }, // Caja
credit: { account: '430', amount: invoice.total }, // Clientes
});
// Notificar al equipo
await slack.send('#contabilidad',
`Factura ${invoice.invoice_number} cobrada: ${invoice.total}€`
);
break;#Preguntas frecuentes
#¿Qué eventos puedo recibir por webhook?
Los principales: invoice.issued, invoice.paid, invoice.voided, invoice.sent, invoice.created y verifactu.status_updated. Puedes filtrar qué eventos recibes al configurar el webhook.
#¿Qué pasa si mi servidor no responde?
BeeL reintenta con backoff exponencial hasta 5 veces en un período de ~3 horas. Los logs de entrega están disponibles en tu panel para diagnosticar problemas.
#¿Por qué es importante verificar la firma HMAC?
Sin verificación, cualquiera que conozca tu URL de webhook podría enviar eventos falsos — por ejemplo, marcar facturas como pagadas sin que realmente lo estén. La verificación HMAC garantiza que solo BeeL puede enviar eventos válidos.
#¿Puedo usar webhooks sin el SDK?
Sí. El SDK simplifica la verificación con WebhookVerifier, pero puedes implementar HMAC-SHA256 manualmente en cualquier lenguaje. El artículo incluye un ejemplo en Python.
#¿Los webhooks funcionan en desarrollo local?
Sí, usando herramientas como ngrok o localtunnel para exponer tu servidor local a internet. Configura la URL temporal de ngrok como endpoint en BeeL.
¿Listo para recibir eventos en tiempo real? Configura tu primer webhook en el panel de BeeL, instala el SDK (código fuente en GitHub) y empieza a procesar eventos. La documentación tiene la referencia completa de tipos de eventos, y en la página de SDKs encontrarás quickstarts para TypeScript, Java y Python. También puedes procesar webhooks con n8n sin escribir código.
