Todos los artículos
Discordwebhook debugginglocal testingbots

Webhooks de Discord vs interacciones, probados en local

Discord llama "webhooks" a dos cosas que no se parecen en nada. Una es una URL de tipo fire-and-forget para publicar mensajes. La otra es un endpoint de interacción que recibe POSTs firmados cada vez que un usuario ejecuta un slash command. El primero funciona en localhost. El segundo nunca. Esta guía trata del segundo — porque es el que necesita un túnel.

Los dos sabores de webhook de Discord

Los webhooks salientes son URLs a las que haces POST. Envías un mensaje JSON y aparece en un canal. No hace falta túnel porque el tráfico sale de tu máquina hacia fuera.

Los endpoints de interacción van en sentido contrario. Registras una URL en Discord; Discord hace POST cuando un usuario ejecuta /tucomando. El endpoint debe ser HTTPS, responder en menos de tres segundos y verificar una firma Ed25519 en cada petición. Localhost no puede hacer nada de eso por sí solo.

Por qué los endpoints de interacción son más estrictos que la media

Discord no solo quiere HTTPS — exige que demuestres que controlas el endpoint antes de aceptar la URL. La verificación ocurre en el registro: Discord envía una interacción PING y tu servidor debe responder con un PONG firmado. Si la verificación Ed25519 falla aunque sea una vez, Discord se niega a guardar la URL.

No vas a clavar la parte de Ed25519 al primer intento. Casi nadie lo hace.

Verificación Ed25519, la parte que muerde

Discord usa Ed25519 (no HMAC SHA-256) y proporciona dos cabeceras en cada interacción:

  • X-Signature-Ed25519 — la firma en hex
  • X-Signature-Timestamp — el timestamp en segundos

Firmas timestamp + rawBody con la clave pública que Discord te muestra en el portal de desarrolladores. La mayoría de los lenguajes tiene una librería para esto. En Node:

import nacl from 'tweetnacl';

const valid = nacl.sign.detached.verify(
  Buffer.from(timestamp + rawBody),
  Buffer.from(signature, 'hex'),
  Buffer.from(PUBLIC_KEY, 'hex'),
);

La causa número uno de verificación fallida es — otra vez — un middleware que parsea el cuerpo antes de que este código corra. Necesitas la cadena cruda que Discord envió, no una versión re-serializada del objeto parseado.

Paso a paso: endpoint de interacción en localhost

  1. Arranca el servidor HTTP de tu bot en local (cualquier puerto).
  2. Ejecuta npx portpreview 3000 para obtener una URL HTTPS pública.
  3. En el portal de desarrolladores de Discord, abre tu aplicación y pega la URL del túnel más tu ruta de interacciones en Interactions Endpoint URL.
  4. Pulsa Save. Discord dispara de inmediato un PING. Si tu verificación Ed25519 funciona, la URL se guarda. Si no, el campo muestra un error.
  5. Una vez guardada, ejecuta un slash command en cualquier servidor donde viva tu bot. La petición llega a tu handler.

Si el guardado falla una y otra vez, inspecciona la petición capturada — las cabeceras, el cuerpo y la respuesta de tu servidor. Nueve de cada diez veces el cuerpo fue mutado en silencio por un middleware JSON antes de la verificación.

El plazo de tres segundos

Discord exige una respuesta en menos de tres segundos o cuenta la interacción como fallida. Para cualquier cosa que tarde más, envía una respuesta inicial de tipo 5 (diferida) y luego haz PATCH a la interacción original con la respuesta real más tarde. Probar en local deja claro qué handlers son demasiado lentos — la captura de peticiones te muestra la latencia exacta.

Los webhooks salientes siguen siendo útiles para probar

Si solo quieres enviar mensajes a un canal desde código local, la URL de webhook saliente funciona sin túnel. Haces POST de JSON, aparece el mensaje. Lo usamos para enviar alertas de logs desde ejecuciones de dev locales.

Cuándo se te queda corto este montaje

Para un solo bot con un puñado de comandos, probar en local con un túnel es de sobra. Si mantienes un bot público que sirve a millones de usuarios, acabarás queriendo un despliegue de staging con una URL estable para que Discord no tenga que volver a verificar cada vez que rota la sesión del túnel.

El timeout de tres segundos de Discord es de la misma familia que la ventana de diez segundos de GitHub. Lee nuestra visión sobre reintentos de webhook e idempotencia para la versión larga. Únete a la lista de espera de PortPreview para tener túnel + captura en una sola CLI.

Preguntas frecuentes

¿Puedo probar slash commands de Discord en localhost?
No directamente. Discord exige HTTPS, verificación de firma Ed25519 y una ventana de respuesta de tres segundos. Usa un túnel para exponer tu servidor local por HTTPS y configúralo como endpoint de interacciones en el portal de desarrolladores.
¿Por qué Discord rechaza la URL de mi endpoint de interacciones?
Cuando guardas la URL, Discord dispara una interacción PING de inmediato. Si tu verificación Ed25519 falla o tu handler no responde con un PONG válido, el guardado se rechaza. La causa más común es un middleware que parsea el cuerpo de la petición antes de la verificación.
¿Los webhooks salientes de Discord necesitan túnel?
No. Los webhooks salientes son URLs a las que haces POST desde tu código, así que el tráfico solo sale. Solo los endpoints de interacción (que reciben POSTs de Discord) necesitan túnel.