Todos os artigos
Discordwebhook debugginglocal testingbots

Webhooks do Discord vs interações, testados em local

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 hex
  • X-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

  1. Suba o servidor HTTP do seu bot em local (qualquer porta).
  2. Rode npx portpreview 3000 para obter uma URL HTTPS pública.
  3. 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.
  4. Clique em Save. O Discord dispara um PING na hora. Se a sua verificação Ed25519 funcionar, a URL é salva. Se não, o campo mostra um erro.
  5. 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.

Perguntas frequentes

Posso testar slash commands do Discord no localhost?
Não diretamente. O Discord exige HTTPS, verificação de assinatura Ed25519 e uma janela de resposta de três segundos. Use um túnel para expor o seu servidor local por HTTPS e configure-o como endpoint de interações no portal de desenvolvedores.
Por que o Discord rejeita a URL do meu endpoint de interações?
Quando você salva a URL, o Discord dispara uma interação PING na hora. Se a sua verificação Ed25519 falhar ou o seu handler não responder com um PONG válido, o salvamento é rejeitado. A causa mais comum é um middleware que faz o parse do corpo da requisição antes da verificação.
Webhooks de saída do Discord precisam de túnel?
Não. Webhooks de saída são URLs para as quais você faz POST a partir do seu código, então o tráfego só vai para fora. Apenas os endpoints de interação (que recebem POSTs do Discord) precisam de túnel.