Errores y Solución de Problemas de Webhook
Referencia completa de códigos de respuesta HTTP, comportamiento de reintentos, errores de verificación de firma y un diagrama de flujo de diagnóstico paso a paso.
Errores y Solución de Problemas de Webhook
La entrega de webhooks es asíncrona. Tu punto final debe ser accesible públicamente, responder dentro de la ventana de tiempo límite y devolver los códigos de estado HTTP correctos. Cuando algo sale mal — errores de red, discrepancias de firma o fallos del punto final — Leypal reintenta la entrega automáticamente usando retroceso exponencial.
Esta guía cubre tres categorías de problemas que puedes encontrar:
- Códigos de respuesta HTTP — qué significa cada código y cómo reacciona Leypal
- Errores de verificación de firma — causas comunes y soluciones para fallos de autenticación
- Problemas de entrega — accesibilidad, tiempos de espera y fallos permanentes
Usa el diagrama de flujo de solución de problemas para diagnosticar fallos desconocidos, y la lista de verificación de producción antes de poner en marcha.
Qué Envía Leypal
Cada webhook es una solicitud HTTPS POST con las siguientes propiedades:
| Propiedad | Valor |
|---|---|
| Método | POST |
Content-Type | application/json |
X-Leypal-Signature | Firma HMAC-SHA256 del cuerpo de la solicitud sin procesar |
| Cuerpo | Payload JSON (consulta Eventos de Webhook para la estructura completa) |
| Tiempo límite | 30 segundos — Leypal cierra la conexión después de 30 segundos sin respuesta |
Importante: Leypal firma los bytes del cuerpo de la solicitud sin procesar, no una cadena JSON serializada. Tu punto final debe leer el cuerpo sin procesar antes de analizarlo para verificar la firma correctamente.
Para los detalles de implementación de la verificación de firma, consulta Verificación de Firma de Webhook.
Códigos de Respuesta HTTP
Leypal trata cualquier respuesta 2xx como una entrega exitosa. Cualquier respuesta que no sea 2xx (incluyendo redirecciones y tiempos de espera) activa la política de reintentos.
2xx — Éxito (Sin Reintento)
| Código | Estado | Comportamiento | Tu Acción |
|---|---|---|---|
200 | OK | Webhook entregado con éxito | Respuesta predeterminada — devuelve esto cuando recibes y confirmas el webhook |
201 | Created | Webhook procesado; recurso creado | Aceptable si tu manejador crea un recurso como parte del procesamiento |
202 | Accepted | Webhook en cola para procesamiento en segundo plano | Respuesta ideal — señala que recibiste el payload y lo procesarás de forma asíncrona |
204 | No Content | Webhook recibido; sin cuerpo de respuesta | Aceptable si prefieres no enviar un cuerpo de respuesta |
Mejor práctica: Devuelve
200o202inmediatamente, antes de ejecutar cualquier lógica de negocio. Procesar el webhook en el manejador de solicitudes (p. ej., consultar la base de datos, enviar correos) añade latencia y arriesga superar el tiempo límite de 30 segundos. En su lugar, encola el evento y procésalo en un trabajo en segundo plano.
3xx — Redirecciones (Fallo + Reintento)
| Código | Estado | Comportamiento | Tu Acción |
|---|---|---|---|
301 | Moved Permanently | Webhook marcado como fallido; Leypal NO sigue redirecciones | Actualiza la URL del webhook en el panel de control al destino final |
302 | Found | Igual que 301 | Actualiza la URL del webhook para evitar la redirección |
307 | Temporary Redirect | Igual que 301 | Verifica que la URL del punto final sea la URL canónica |
Las redirecciones indican una mala configuración. Leypal reintentará hasta 5 veces, pero cada intento llega a la misma redirección. Actualiza la URL del webhook en tu panel de control de Claves API para apuntar directamente al punto final final.
4xx — Errores del Cliente (Fallo + Reintento)
| Código | Estado | Comportamiento | Tu Acción |
|---|---|---|---|
400 | Bad Request | El punto final rechazó el formato de la solicitud | Verifica tu lógica de verificación de firma; verifica el orden del middleware |
401 | Unauthorized | Verificación de firma fallida o encabezado faltante | Consulta Errores de Verificación de Firma |
403 | Forbidden | El punto final existe pero rechazó la solicitud | Verifica tu lógica de autorización; confirma que la solicitud es de Leypal |
404 | Not Found | La ruta del webhook no existe | Verifica la URL del punto final en el panel de control; revisa el enrutamiento del servidor |
408 | Request Timeout | El punto final tardó más de 30 segundos en responder | Devuelve 202 inmediatamente; mueve el procesamiento a un trabajo en segundo plano |
429 | Too Many Requests | Límite de velocidad superado en tu punto final | Implementa una exención de límite de velocidad para el rango de IPs de Leypal, o eleva tus límites |
Todas las respuestas 4xx se reintentan hasta 5 veces. Leypal asume que el error puede ser transitorio (p. ej., una sobrecarga breve o una condición de carrera). Corrige la causa raíz — los reintentos eventualmente tendrán éxito o agotarán los intentos.
401 y 403 son los códigos más comunes durante la configuración inicial. Si los ves, ve directamente a Errores de Verificación de Firma.
5xx — Errores del Servidor (Fallo + Reintento)
| Código | Estado | Comportamiento | Tu Acción |
|---|---|---|---|
500 | Internal Server Error | El punto final lanzó una excepción no manejada | Revisa los registros del servidor; corrige el error y vuelve a desplegar |
502 | Bad Gateway | El proxy inverso no pudo llegar a tu aplicación | Verifica que el servidor de la aplicación esté funcionando; revisa la configuración de nginx/caddy/ALB |
503 | Service Unavailable | Punto final sobrecargado o en modo de mantenimiento | Escala hacia arriba o reduce el tiempo de procesamiento por solicitud |
504 | Gateway Timeout | El proxy desistió de esperar a tu aplicación | Devuelve 202 inmediatamente; reduce el tiempo de ejecución del manejador |
Todas las respuestas 5xx se reintentan hasta 5 veces. Estas son temporales por definición — la suposición es que tu servidor se recuperará. Revisa los registros de tu aplicación para ver los stack traces.
Errores de Red (Fallo + Reintento)
| Tipo de Error | Comportamiento | Tu Acción |
|---|---|---|
| Conexión rechazada | Reintentado hasta 5 veces | Verifica que el punto final esté ejecutándose y escuchando en el puerto correcto |
| Fallo de resolución DNS | Reintentado hasta 5 veces | Verifica que el DNS del dominio esté configurado; verifica con dig tupuntofinalf.com |
| Tiempo de espera de conexión | Reintentado hasta 5 veces | Verifica que el firewall permita HTTPS entrante (puerto 443) |
| Error TLS/SSL | Reintentado hasta 5 veces | Asegúrate de que el certificado SSL sea válido; verifica la cadena de certificados |
Nota de desarrollo local: Leypal no puede llegar a
localhosto127.0.0.1. Usa ngrok o localtunnel para crear un túnel público a tu servidor local.
Política de Reintentos
Cuando falla una entrega de webhook, Leypal reintenta automáticamente con retroceso exponencial:
| Intento | Retroceso | Tiempo Acumulado Después del Primer Fallo |
|---|---|---|
| 1 | Entrega inicial (sin retroceso) | 0 min |
| 2 | ~5 minutos | ~5 min |
| 3 | ~10 minutos | ~15 min |
| 4 | ~20 minutos | ~35 min |
| 5 | ~40 minutos | ~75 min |
Después del 5° intento fallido, el webhook se marca como permanentemente fallido. No se realizan más reintentos. Para recibir el evento de nuevo, debes activar la acción que lo causó (p. ej., crear una nueva solicitud de firma).
Un webhook se marca como exitoso cuando:
- Leypal recibe una respuesta
2xxde tu punto final antes del tiempo límite de 30 segundos
Un webhook se marca como fallido cuando:
- Los 5 intentos se agotan sin una respuesta
2xx - Cada intento resulta en un error de red (punto final inaccesible)
Nota de idempotencia: Dado que Leypal reintenta en caso de fallo, tu punto final puede recibir el mismo evento más de una vez. Siempre implementa procesamiento idempotente — usa el campo
webhookIdpara detectar y omitir entregas duplicadas.
Errores de Verificación de Firma
Estos errores ocurren cuando tu punto final devuelve 401 Unauthorized o cuando tu verificación de firma falla silenciosamente.
| Error | Causa | Síntoma | Solución |
|---|---|---|---|
| Firma inválida | El HMAC calculado no coincide con el encabezado X-Leypal-Signature | El punto final devuelve 401; los registros muestran discrepancia de firma | Consulta las soluciones a continuación |
Encabezado X-Leypal-Signature faltante | Webhook no registrado o secreto no generado | Encabezado ausente en req.headers | Verifica la URL del webhook y el secreto en el panel de control de Claves API |
| Punto final no accesible | Leypal no puede conectarse a tu URL | El webhook aparece como fallido en el historial; responseCode es null | Revisa firewall, DNS, disponibilidad del punto final |
| Algoritmo inválido | Usando SHA1, MD5 u otro algoritmo en lugar de SHA256 | Discrepancia en el formato de la cadena hexadecimal de firma | Siempre usa sha256 con crypto.createHmac de Node.js |
Solucionar "Firma Inválida" (401)
La causa más común es una discrepancia entre cómo calculas el HMAC y cómo lo calcula Leypal. Verifica estos puntos en orden:
1. Secreto de webhook incorrecto u obsoleto
El secreto en tu .env debe coincidir exactamente con el que aparece en tu panel de control de Claves API de Leypal. Incluso un espacio extra o un carácter de más hará que cada webhook falle.
# Imprime tu secreto actual (sin salto de línea al final)
echo -n "$WEBHOOK_SECRET" | wc -c
# Compara con el recuento de caracteres mostrado en tu panel de controlSi rotaste recientemente el secreto en el panel de control, actualiza tu .env y vuelve a desplegar inmediatamente.
2. No leer el cuerpo sin procesar
El HMAC se calcula sobre los bytes de la solicitud sin procesar — antes del análisis JSON. Si dejas que Express analice el cuerpo primero, los bytes sin procesar se pierden y no puedes recalcular la firma correcta.
// INCORRECTO — cuerpo JSON analizado; bytes sin procesar perdidos
app.use(express.json());
app.post('/webhooks', (req, res) => {
// req.body ya es un objeto; no se pueden recuperar los bytes sin procesar
const sig = computeHmac(webhookSecret, JSON.stringify(req.body)); // FALLA
});
// CORRECTO — cuerpo sin procesar preservado para la ruta de webhook
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
// req.body es un Buffer que contiene los bytes sin procesar
const sig = crypto.createHmac('sha256', webhookSecret)
.update(req.body) // Buffer, no cadena
.digest('hex');
// ahora compara con el encabezado X-Leypal-Signature
});3. Usar igualdad de cadenas en lugar de comparación segura en tiempo
// INCORRECTO — ataque de temporización posible
if (computedSig === req.headers['x-leypal-signature']) { ... }
// CORRECTO — seguro en tiempo
const a = Buffer.from(computedSig);
const b = Buffer.from(req.headers['x-leypal-signature']);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send('Invalid signature');
}Para una implementación completa, consulta Verificación de Firma de Webhook.
Solucionar "Encabezado X-Leypal-Signature Faltante"
Si el encabezado está ausente en cada solicitud, es posible que el webhook no esté registrado:
- Ve a tu panel de control de Claves API → sección Webhooks
- Verifica que la URL de tu punto final esté listada y Activa
- Verifica que se haya generado un secreto de webhook (haz clic en el campo del secreto — debería mostrar
sk_webhook_xxx) - Si el secreto fue eliminado o nunca se creó, haz clic en Generar Secreto y copia el nuevo valor a tu
.env
API de Historial de Entrega de Webhook
Cuando un webhook no llega, el historial de entregas es el primer lugar donde buscar. Muestra exactamente qué envió Leypal, qué devolvió tu punto final y por qué falló la entrega.
Punto final: GET /api/v1/webhook-notifications
Autenticación: Clave API como token Bearer
curl -X GET https://api.leypal.dev/api/v1/webhook-notifications \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json"Parámetros de consulta opcionales:
| Parámetro | Tipo | Descripción |
|---|---|---|
event | string | Filtrar por tipo de evento (p. ej., signature.created) |
status | string | Filtrar por estado de entrega: success, failed, pending |
limit | number | Resultados por página (predeterminado: 50, máx: 100) |
offset | number | Desplazamiento de paginación |
Campos de respuesta por registro de entrega:
| Campo | Tipo | Descripción |
|---|---|---|
id | string | ID del registro de notificación |
event | string | Tipo de evento (p. ej., signature.completed) |
webhookId | string | ID único de entrega del webhook (coincide con el encabezado X-Leypal-Webhook-Id) |
organizationId | string | Tu ID de organización |
status | string | success | failed | pending |
requestBody | object | Payload JSON exacto que Leypal envió |
responseCode | number | Código de estado HTTP que devolvió tu punto final |
responseBody | string | Cuerpo de respuesta que envió tu punto final |
responseHeaders | object | Encabezados de respuesta que envió tu punto final |
deliveredAt | string | Marca de tiempo ISO 8601 del intento de entrega |
nextRetryAt | string | Marca de tiempo ISO 8601 del próximo reintento (si está pendiente/fallido) |
failureReason | string | Por qué falló el webhook (p. ej., "connection timeout") |
attempts | number | Total de intentos de entrega realizados hasta ahora |
Cuándo usar esto: Si un webhook nunca llega, consulta este punto final para ver si Leypal intentó la entrega. Si no existe ningún registro, el evento desencadenante no se activó. Si existe un registro, responseCode y failureReason te dicen exactamente qué salió mal.
Diagrama de Flujo de Solución de Problemas
Usa este diagrama de flujo para diagnosticar problemas de entrega de webhook de forma sistemática:
Mi webhook no se está entregando
│
├─ Paso 1: ¿Está la URL del webhook registrada en tu panel de control de Claves API?
│ ├─ No → Ve al panel de control de Claves API → Webhooks → Añadir URL → Generar Secreto
│ └─ Sí → Continúa al Paso 2
│
├─ Paso 2: ¿Es tu punto final accesible públicamente desde internet?
│ ├─ No (localhost/privado) → Usa ngrok o localtunnel para pruebas
│ │ Despliega en un servidor público para producción
│ └─ Sí → Continúa al Paso 3
│
├─ Paso 3: ¿Intentó Leypal la entrega?
│ │ Verifica: GET /api/v1/webhook-notifications?status=failed
│ ├─ No se encontraron registros → El evento desencadenante no se activó
│ │ Verifica que realizaste la acción (p. ej., creaste una firma)
│ └─ Registros encontrados → Continúa al Paso 4
│
├─ Paso 4: ¿Qué muestra el registro de entrega?
│ ├─ responseCode: 2xx → El webhook fue entregado; revisa los registros de tu propia aplicación
│ ├─ responseCode: 401 → Verificación de firma fallando
│ │ Consulta: sección Errores de Verificación de Firma
│ ├─ responseCode: 4xx → El punto final rechazó la solicitud
│ │ Consulta: sección Códigos de Respuesta HTTP
│ ├─ responseCode: 5xx → El punto final falló o devolvió un error
│ │ Verifica: Registros de tu servidor para stack traces
│ └─ responseCode: null → Error de red; Leypal no pudo conectarse
│ Continúa al Paso 5
│
├─ Paso 5: ¿Puede Leypal llegar a tu punto final?
│ │ Prueba: curl -X POST https://tupuntofinalf.com/webhooks -d '{}' -v
│ ├─ curl falla → Corrige: reglas de firewall, DNS, certificado SSL, ¿servidor ejecutándose?
│ └─ curl tiene éxito → El manejador del webhook falla al inicio o al principio de la solicitud
│ Verifica: Registros de la aplicación para errores antes de que se envíe la respuesta
│
├─ Paso 6: ¿Es la verificación de firma el problema?
│ │ Verifica: ¿Es responseCode 401 en el historial?
│ ├─ Sí → Consulta: sección Errores de Verificación de Firma arriba
│ └─ No → Revisa los registros de la aplicación para errores dentro del manejador del webhook
│
└─ ¿Sigues sin poder resolver el problema?
├─ Habilita el registro detallado en tu manejador de webhook
├─ Revisa el historial de webhooks para ver el requestBody y responseBody exactos
├─ Verifica tu punto final con: curl -X POST <url> -H "Content-Type: application/json" -d '{"test":true}'
└─ Contacta al soporte de Leypal con:
- Tipo de evento que no se está entregando
- Tu ID de Organización
- ID del Webhook (del API de historial)
- Marca de tiempo de la entrega esperada
- Registros del servidor que muestran lo que recibió tu punto finalEscenarios Comunes y Soluciones
| Escenario | Lo Que Ves | Causa Raíz | Solución |
|---|---|---|---|
| El punto final devuelve 200 pero el webhook aparece como fallido | Historial: status: failed, responseCode: 200 | El punto final tardó más de 30 segundos en responder antes de devolver 200 | Devuelve 202 en menos de 1 segundo; mueve toda la lógica de negocio a una cola en segundo plano |
| "Firma inválida" aunque el secreto es correcto | Cada webhook devuelve 401 Unauthorized | Cuerpo sin procesar no preservado; o igualdad de cadenas usada | Añade express.raw() antes del middleware JSON; usa crypto.timingSafeEqual() |
| El webhook funcionaba ayer, se detuvo hoy | No se entregan webhooks desde un momento específico | El punto final se cayó, o el secreto del webhook fue rotado | Verifica si el punto final está ejecutándose; compara el secreto en .env vs panel de control |
| El webhook de prueba tiene éxito pero los webhooks reales fallan | Prueba manual: entregada; evento real: fallido | URL de punto final diferente (dev vs prod) o diferencia de firewall | Verifica que la URL del webhook de producción esté registrada; revisa el firewall de producción |
| Obteniendo 404 en la entrega del webhook | Historial: responseCode: 404 | La ruta del webhook no existe en el servidor | Verifica la ruta de URL en el panel de control; revisa la configuración de enrutamiento del servidor |
| Leypal sigue mostrando reintentos pendientes después de la corrección | Historial: status: pending incluso después de que el punto final está saludable | Los reintentos pendientes están en un horario — se ejecutarán en nextRetryAt | Espera el próximo reintento; o activa un nuevo evento para generar un webhook fresco |
| El webhook fue entregado pero el tipo de evento no fue manejado | La aplicación ignora silenciosamente el evento | El bloque switch/if no cubre el tipo de evento | Registra todos los tipos de eventos entrantes; añade un manejador para el evento faltante |
| El mismo webhook fue entregado dos veces | Entradas duplicadas en tu base de datos | El reintento tuvo éxito; o la red lo entregó dos veces | Implementa idempotencia: verifica webhookId antes de procesar, omite duplicados |
Lista de Verificación de Producción
Antes de desplegar tu manejador de webhook a producción, verifica cada elemento:
- El punto final usa HTTPS (no HTTP)
- La URL del webhook de producción está actualizada en el panel de control de Claves API de Leypal (no la URL de dev/staging)
- El secreto del webhook está almacenado en una variable de entorno o gestor de secretos (no codificado directamente)
- El archivo
.envNO está confirmado en git (añadido a.gitignore) - Usando
crypto.timingSafeEqual()para la comparación de firmas (no===) - Usando el middleware
express.raw({ type: 'application/json' })antes del análisis JSON - El punto final devuelve
200o202en menos de 1 segundo - La lógica de negocio se procesa de forma asíncrona (sistema de cola o trabajo en segundo plano)
-
webhookIdse registra para cada evento entrante (permite la depuración) - El historial de entregas de webhook se monitorea periódicamente (
GET /api/v1/webhook-notifications) - La entrega duplicada se maneja mediante verificación de idempotencia en
webhookId - El manejo de errores está en su lugar — las excepciones no manejadas devuelven
500limpiamente, sin bloquear el proceso
Obtener Ayuda
Si has seguido el diagrama de flujo de solución de problemas y el problema persiste, recopila lo siguiente antes de contactar al soporte:
- Tipo de evento que no se está entregando (p. ej.,
signature.completed) - ID de Organización (visible en tu panel de control)
- ID del Webhook del API de historial de entregas (campo
webhookId) - Marca de tiempo de la entrega esperada
- Registros del servidor del momento del intento de entrega
- Entrada del historial de webhook que muestra
requestBody,responseCodeyfailureReason
Guías Relacionadas
| Guía | Qué Cubre |
|---|---|
| Configuración de Webhook | Registro de puntos finales, generación de secretos, gestión de la configuración de webhook |
| Verificación de Firma de Webhook | Implementación completa de HMAC-SHA256 con ejemplos de código |
| Eventos de Webhook | Estructura completa del payload para todos los tipos de evento |