Qué vas a tener
Un canal de Discord donde aparece un mensaje rojo cuando un workflow falla: repo, rama y enlace al run en GitHub.
No hace falta servidor propio ni bot de Discord. Bastan dos cosas: un Incoming Webhook —una dirección secreta que Discord te da para publicar en un canal sin más permisos— y unas líneas más en tu configuración de GitHub Actions.
- Falla un job en Actions
- Un step hace POST al webhook
- Discord muestra el mensaje
Si aún no tienes claro qué es un webhook, lee antes Introducción a webhooks.
Requisitos
- Un repo en GitHub. No hace falta tener ya workflows: aquí creas el primero
- Un canal de Discord donde seas administrador, que es quien puede crear webhooks
- Permiso para añadir secrets en ese repo — o alguien que te lo cree
Crea el webhook en Discord
- Abre el canal donde quieres el aviso.
- Engranaje del canal → Integraciones → Webhooks → Nuevo webhook (los nombres pueden variar un poco según el cliente).
- Ponle un nombre claro, por ejemplo
CI fallos. - Copiar URL del webhook.
Esa URL es un secreto. Quien la tenga puede escribir en el canal. No la pegues en el chat, en un issue ni en el repo.
Prueba el webhook en local
Antes de Actions, confirma que la URL escribe en el canal. Sustituye todo el valor entre comillas por la URL que te dio Discord (empieza por https://discord.com/api/webhooks/ — no la pegues encima del prefijo).
WEBHOOK_URL="pega-aqui-tu-url-completa"
curl -sS -o /dev/null -w "%{http_code}\
" -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"content\":\"Prueba desde la integración\"}"Esperado: 204 —que en Discord significa «recibido, sin nada que devolver»— y el mensaje visible en el canal.
A partir de aquí desaparecen las pestañas de sistema: los workflows corren en la máquina de GitHub, que es Linux, así que los comandos son siempre los mismos aunque tú estés en Windows.
Guárdala en GitHub Secrets
- En el repo: Settings → Secrets and variables → Actions.
- New repository secret.
- Nombre exacto:
DISCORD_WEBHOOK_URL. - Valor: la URL que copiaste. Guarda.
Si el secret tiene otro nombre, el workflow de abajo no lo verá.
Añade el workflow
Crea .github/workflows/discord-on-failure.yml (el nombre del archivo da igual) con esto:
name: Aviso Discord si falla CI
on:
workflow_dispatch:
jobs:
# Lo dejamos fallando a propósito para probar el aviso.
# Luego lo cambias por tu job real.
fail-on-purpose:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Forzar fallo (solo para probar)
run: exit 1
notify-discord:
needs: fail-on-purpose
if: failure()
runs-on: ubuntu-latest
steps:
- name: Avisar en Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "Falta el secret DISCORD_WEBHOOK_URL."
exit 1
fi
payload=$(jq -n \
--arg repo "${{ github.repository }}" \
--arg branch "${{ github.ref_name }}" \
--arg url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
'{
embeds: [{
title: "GitHub Actions falló",
description: ("**" + $repo + "** · rama `" + $branch + "`"),
url: $url,
color: 15158332
}]
}')
curl -sS -X POST \
-H "Content-Type: application/json" \
-d "$payload" \
"$DISCORD_WEBHOOK_URL"Lo que merece atención de ese bloque:
| Pieza | Para qué |
|---|---|
needs: + if: failure() | La pareja que hace el truco: needs dice a qué job espera y if: failure() que solo se ejecute si aquel falló. Sin esto avisaría siempre |
DISCORD_WEBHOOK_URL | Saca la URL de GitHub Secrets. Nunca se escribe en el archivo: el YAML se ve en el repo |
jq + curl | Arman el mensaje y lo envían. Vienen instalados en la máquina de GitHub, no hay que añadir nada |
color: 15158332 | El rojo de la barra lateral del mensaje, en decimal. Cámbialo si prefieres otro |
Pruébalo
- Crea el archivo en el repo: Add file → Create new file → ruta `.github/workflows/discord-on-failure.yml` → pega el YAML → Commit (o súbelo con git).
- En Actions, abre Aviso Discord si falla CI → Run workflow.
- Espera a que fail-on-purpose falle (es normal) y a que notify-discord termine en verde.
- Mira el canal de Discord: deberías ver el embed rojo con repo, rama y enlace.
Adáptalo a tu CI
Cuando el aviso de prueba te funcione, deja de usar fail-on-purpose. Abajo tienes YAML listo para pegar: solo adapta el comando de tu job (o los nombres) y el secret sigue siendo el mismo.
Un job (lo habitual)
Cambia npm test por tu comando real. Puedes pegar este bloque entero:
name: Aviso Discord si falla CI
on:
workflow_dispatch:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
notify-discord:
needs: test
if: failure()
runs-on: ubuntu-latest
steps:
- name: Avisar en Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "Falta el secret DISCORD_WEBHOOK_URL."
exit 1
fi
payload=$(jq -n \
--arg repo "${{ github.repository }}" \
--arg branch "${{ github.ref_name }}" \
--arg url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
'{
embeds: [{
title: "GitHub Actions falló",
description: ("**" + $repo + "** · rama `" + $branch + "`"),
url: $url,
color: 15158332
}]
}')
curl -sS -X POST \
-H "Content-Type: application/json" \
-d "$payload" \
"$DISCORD_WEBHOOK_URL"needs: test = espera a test.
if: failure() = solo corre si test falló.
Si test pasa en verde, Discord no recibe nada.
Varios jobs
Igual, pero el aviso espera a varios jobs. Discord avisa si falla cualquiera:
name: Aviso Discord si falla CI
on:
workflow_dispatch:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
notify-discord:
needs: [test, build, lint]
if: failure()
runs-on: ubuntu-latest
steps:
- name: Avisar en Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "Falta el secret DISCORD_WEBHOOK_URL."
exit 1
fi
payload=$(jq -n \
--arg repo "${{ github.repository }}" \
--arg branch "${{ github.ref_name }}" \
--arg url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
'{
embeds: [{
title: "GitHub Actions falló",
description: ("**" + $repo + "** · rama `" + $branch + "`"),
url: $url,
color: 15158332
}]
}')
curl -sS -X POST \
-H "Content-Type: application/json" \
-d "$payload" \
"$DISCORD_WEBHOOK_URL"Cuándo se dispara
No es otro flujo: es el bloque on: del mismo archivo.
Qué pones en on: | Efecto |
|---|---|
Solo workflow_dispatch | Lo lanzas a mano (como en la prueba) |
Solo push a main | Corre solo al hacer push a main |
| Los dos | Puedes probar a mano y recibir avisos reales en main |
Los ejemplos de “un job” / “varios jobs” ya llevan los dos. Si aún usas el YAML de prueba (solo manual), añade el push cuando el mensaje te convenza:
on:
workflow_dispatch:
push:
branches: [main]Seguridad
- El secret debe vivir solo en GitHub Secrets (o en el almacén de secretos de tu org).
- Este flujo no verifica firmas de GitHub: Discord confía en quien conozca la URL.
- No dispares el aviso en cada step ruidoso; un mensaje por fallo de job suele bastar. Discord puede responder
429si spameas.
Errores habituales
| Qué ves | Causa habitual | Qué hacer |
|---|---|---|
notify-discord ni aparece | El job anterior no falló, o no está en needs | Mira el grafo del run; if: failure() solo actúa tras un fallo |
El step falla con URL rara / curl exit ≠ 0 | Secret mal pegado (espacios, comillas, URL a medias) | Reescribe el secret entero; no lo “arregles” a ojo en el log |
| 404 desde Discord | Webhook borrado o URL vieja | Crea otro webhook y actualiza el secret |
| 401 / sin permiso | Menos habitual en Incoming Webhooks; URL incompleta a veces se comporta raro | Regenera la URL |
| Todo verde en Actions y silencio en Discord | Miras otro canal, o el job avisado no falló | Confirma el canal del webhook y el grafo del run |
| Mensaje duplicado | Reejecutaste el workflow | Normal aquí; no hay deduplicación |
Alternativas
- Email de GitHub
- Llega, pero se pierde entre notificaciones. Menos útil en el canal del equipo.
- Webhook de GitHub con sufijo /github en Discord
- Eventos del repo (push, PR, etc.); no es el mismo control que «falló este job de Actions».
- Bot de Telegram + Actions
- Mismo aviso de CI hacia un chat, con token y chat_id.
- Slack Incoming Webhook
- Mismo patrón (POST con una URL secreta), otro destino.