O Discord chama de "webhooks" duas coisas que não se parecem em nada. Uma é uma URL do tipo dispara-e-esquece para postar mensagens. A outra é um endpoint de interação que recebe POSTs assinados toda vez que um usuário roda um slash command. A primeira funciona no localhost. A segunda nunca. Este guia é sobre a segunda — porque é ela que precisa de um túnel.
Os dois sabores de webhook do Discord
Os webhooks de saída são URLs para as quais você faz POST. Você envia uma mensagem JSON e ela aparece num canal. Sem túnel, porque o tráfego sai da sua máquina para fora.
Os endpoints de interação vão na direção contrária. Você registra uma URL no Discord; o Discord faz POST nela sempre que um usuário roda /seucomando. O endpoint precisa ser HTTPS, responder em menos de três segundos e verificar uma assinatura Ed25519 em cada requisição. O localhost não faz nada disso sozinho.
Por que os endpoints de interação são mais rígidos que a média
O Discord não quer só HTTPS — ele exige que você prove que controla o endpoint antes de aceitar a URL. A verificação acontece no registro: o Discord envia uma interação PING e o seu servidor precisa responder com um PONG assinado. Se a verificação Ed25519 falhar uma única vez, o Discord se recusa a salvar a URL.
Você não vai acertar a parte do Ed25519 na primeira tentativa. Quase ninguém acerta.
Verificação Ed25519, a parte que morde
O Discord usa Ed25519 (não HMAC SHA-256) e fornece dois cabeçalhos em cada interação:
X-Signature-Ed25519— a assinatura em hexX-Signature-Timestamp— o timestamp em segundos
Você assina timestamp + rawBody com a chave pública que o Discord te mostra no portal de desenvolvedores. A maioria das linguagens tem uma biblioteca para isso. No Node:
import nacl from 'tweetnacl';
const valid = nacl.sign.detached.verify(
Buffer.from(timestamp + rawBody),
Buffer.from(signature, 'hex'),
Buffer.from(PUBLIC_KEY, 'hex'),
);
A causa número um de verificação falha é — de novo — um middleware que faz o parse do corpo antes deste código rodar. Você precisa da string crua que o Discord enviou, não de uma versão re-serializada do objeto já parseado.
Passo a passo: endpoint de interação no localhost
- Suba o servidor HTTP do seu bot em local (qualquer porta).
- Rode
npx portpreview 3000para obter uma URL HTTPS pública. - No portal de desenvolvedores do Discord, abra a sua aplicação e cole a URL do túnel mais o caminho de interações em Interactions Endpoint URL.
- Clique em Save. O Discord dispara um
PINGna hora. Se a sua verificação Ed25519 funcionar, a URL é salva. Se não, o campo mostra um erro. - Uma vez salva, rode um slash command em qualquer servidor onde o seu bot esteja. A requisição cai no seu handler.
Se o salvamento falhar repetidamente, inspecione a requisição capturada — os cabeçalhos, o corpo e a resposta do seu servidor. Nove em cada dez vezes o corpo foi mutado em silêncio por um middleware JSON antes da verificação.
O prazo de três segundos
O Discord exige uma resposta em menos de três segundos ou conta a interação como falha. Para qualquer coisa que demore mais, envie uma resposta inicial do tipo 5 (diferida) e depois faça PATCH na interação original com a resposta real mais tarde. Testar em local deixa óbvio quais handlers estão lentos demais — a captura de requisições te mostra a latência exata.
Os webhooks de saída ainda são úteis para testar
Se você só quer enviar mensagens para um canal a partir de código local, a URL de webhook de saída funciona sem túnel. Faz POST de JSON, a mensagem aparece. Usamos isso para mandar alertas de log das execuções de dev locais.
Quando você supera esse arranjo
Para um único bot com um punhado de comandos, testar em local com um túnel é mais que suficiente. Se você mantém um bot público que atende milhões de usuários, vai acabar querendo um deploy de staging com uma URL estável para que o Discord não tenha que verificar de novo toda vez que a sessão do túnel rotaciona.
O timeout de três segundos do Discord é da mesma família que a janela de dez segundos do GitHub. Leia a nossa visão sobre retries de webhook e idempotência para a versão longa. Entre na lista de espera do PortPreview para ter túnel + captura numa só CLI.