Webhooks de GitHub: configuración, eventos y firma | We.Inc
Crea un webhook de GitHub, elige eventos, verifica la cabecera X-Hub-Signature-256 con código que funciona y reenvía las entregas fallidas. Incluye un comprobador de firmas en el navegador.
Un webhook de GitHub es un mensaje que GitHub envía a la URL de tu servidor cada vez que ocurre algo en un repositorio, como un push, una nueva issue o una release. Lo añades en Settings, Webhooks del repositorio. Define un secreto para poder comprobar la cabecera X-Hub-Signature-256 y rechazar entregas falsas.
Datos útiles sobre los webhooks de GitHub (docs.github.com, octubre de 2026)
Elemento
Lo que documenta GitHub
Cabecera de firma
X-Hub-Signature-256, el valor empieza por sha256=
Método de firma
Digest HMAC en hexadecimal con SHA-256, con tu secreto como clave
Tiempo de respuesta
Más de 10 segundos se registra como entrega fallida
Reintentos automáticos
Ninguno; las entregas fallidas no se reenvían automáticamente
Reenvío manual
Entregas de los últimos 3 días
Tamaño máximo del payload
25 MB; los payloads más grandes no se entregan
Los webhooks permiten que GitHub avise a tu web o app de los cambios en el momento en que ocurren, para que puedas reconstruir una web en cada push, publicar un mensaje cuando se abre una issue o actualizar un changelog en cada release. Lo importante, y lo que la mayoría de guías rápidas se saltan, es comprobar que una entrega viene realmente de GitHub. El comprobador de abajo calcula la misma firma que envía GitHub, directamente en tu navegador.
Abre el repositorio, haz clic en Settings, luego en Webhooks en la barra lateral izquierda y después en Add webhook.
Escribe tu endpoint en Payload URL y elige application/json como tipo de contenido para que el cuerpo llegue como JSON simple.
Introduce una cadena aleatoria larga en Secret y guarda el mismo valor en las variables de entorno de tu servidor.
En Which events would you like to trigger this webhook, elige solo el evento push o selecciona eventos concretos, marca Active y guarda.
GitHub envía un evento ping de inmediato; revísalo en Recent Deliveries para confirmar que tu servidor respondió.
Responde rápido. Devuelve un estado 2xx enseguida y haz el trabajo lento después en una tarea en segundo plano, porque una respuesta de más de 10 segundos cuenta como fallo.
Calcula la firma sobre los bytes del cuerpo sin procesar, antes de parsear el JSON. Parsear y volver a serializar cambia los bytes y la comprobación falla.
Compara las firmas con una función de tiempo constante como crypto.timingSafeEqual, nunca con una comparación == simple.
Usa X-GitHub-Delivery, un ID único por entrega, para saltarte duplicados cuando reenvíes a mano.
Suscríbete solo a los eventos que gestionas. Menos eventos significa menos peticiones a tu servidor y menos cosas que proteger.
Preguntas frecuentes
¿GitHub reintenta los webhooks fallidos?
No. La documentación de GitHub indica que no reenvía automáticamente las entregas fallidas. Puedes reenviar una a mano desde Recent Deliveries si se hizo en los últimos 3 días.
¿Qué es la cabecera X-Hub-Signature-256?
Se envía cuando el webhook tiene un secreto. GitHub toma el cuerpo de la petición, calcula un HMAC con SHA-256 usando tu secreto como clave y envía el resultado en hexadecimal con el prefijo sha256=. Tu servidor repite el cálculo y compara.
¿Qué eventos puede recibir un webhook?
Muchos, entre ellos push, pull_request, issues, release y workflow_run. La cabecera X-GitHub-Event indica el evento que disparó cada entrega, y ping llega cuando creas el hook por primera vez.
¿Puedo probar un webhook sin un servidor público?
Tu endpoint debe ser accesible desde internet, porque GitHub le envía las peticiones. Para trabajar en local, usa una herramienta de túnel que dé a tu máquina una URL pública temporal y úsala como Payload URL.
¿Webhook o consultar la API periódicamente?
Un webhook te envía cada cambio en cuanto ocurre; el polling consiste en preguntar a la API cada cierto tiempo. Los webhooks usan menos peticiones y llegan antes, lo que importa porque las llamadas a la API cuentan para los límites de uso.
Qué llega con cada entrega
Cada entrega es un POST con cabeceras que la describen: X-GitHub-Event indica el evento, X-GitHub-Delivery es un ID único, X-GitHub-Hook-ID identifica el webhook y el User-Agent siempre empieza por GitHub-Hookshot/.
El cuerpo es el payload del evento. En un push enumera los commits y la referencia de la rama, así un script de despliegue puede ignorar los pushes a ramas distintas de la que construye.
Comprobar tu código de verificación con los valores de prueba de GitHub
GitHub publica una entrada conocida para que demuestres que tu código es correcto antes de ponerlo en producción: el secreto It's a Secret to Everybody y el payload Hello, World! deben producir una firma que empieza por 757107ea.
El comprobador de esta página viene relleno con esos valores. Si tu servidor calcula el mismo resultado a partir de ellos, tu código de verificación es correcto.
Esta ruta de Express lee el cuerpo sin procesar, calcula el HMAC con tu secreto y lo compara en tiempo constante antes de confiar en el payload.
express.raw conserva los bytes exactos que firmó GitHub. El secreto viene de una variable de entorno, nunca del código.
Cabeceras de la entrega
Cabeceras de cada entrega de webhook de GitHub
Cabecera
Qué contiene
X-GitHub-Event
El nombre del evento, como push o issues
X-GitHub-Delivery
Un ID único (GUID) para esta entrega
X-GitHub-Hook-ID
El ID del webhook que la envió
X-Hub-Signature-256
sha256= más el HMAC del cuerpo, solo si hay un secreto definido
User-Agent
Siempre empieza por GitHub-Hookshot/
Comprobador de firmas de webhook
Escribe un secreto y un cuerpo de petición sin procesar para ver el valor de X-Hub-Signature-256 que enviaría GitHub. Funciona en tu navegador con la Web Crypto API; no se sube nada.
Secreto del webhook
Cuerpo de la petición sin procesar
Cabecera de firma que recibiste (opcional)
sha256=...
Firma calculada
Coincide: la firma de esta entrega es válida para ese secreto y ese cuerpo.
No coincide: el secreto, los bytes del cuerpo o la cabecera son distintos.
Cargar los valores de prueba de GitHub
Con los valores de prueba de GitHub, el resultado debe ser: