Alle Artikel
Discordwebhook debugginglocal testingbots

Discord-Webhooks vs. Interactions, lokal getestet

Discord nennt zwei Dinge „Webhooks", und sie verhalten sich völlig unterschiedlich. Das eine ist eine Fire-and-forget-URL zum Posten von Nachrichten. Das andere ist ein Interaction-Endpoint, der signierte POSTs empfängt, sobald ein Nutzer einen Slash-Command ausführt. Das erste läuft auf localhost. Das zweite nie. In diesem Leitfaden geht es um das zweite — denn das braucht einen Tunnel.

Die zwei Discord-Webhook-Varianten

Ausgehende Webhooks sind URLs, an die du POSTest. Du schickst eine JSON-Nachricht, sie erscheint in einem Channel. Kein Tunnel nötig, da der Traffic von deinem Rechner nach außen fließt.

Interaction-Endpoints laufen in die Gegenrichtung. Du registrierst eine URL bei Discord; Discord POSTet daran, sobald ein Nutzer /deinbefehl ausführt. Der Endpoint muss HTTPS sein, innerhalb von drei Sekunden antworten und bei jeder Anfrage eine Ed25519-Signatur prüfen. Localhost kann nichts davon allein.

Warum Interaction-Endpoints strenger sind als die meisten

Discord will nicht nur HTTPS — es verlangt, dass du vor der Annahme der URL nachweist, dass du den Endpoint kontrollierst. Der Prüfablauf passiert bei der Registrierung: Discord schickt eine PING-Interaction, dein Server muss mit einem signierten PONG antworten. Schlägt die Ed25519-Prüfung auch nur einmal fehl, weigert sich Discord, die URL zu speichern.

Den Ed25519-Teil bekommst du beim ersten Versuch nicht hin. Fast niemand schafft das.

Ed25519-Prüfung, der Teil, der beißt

Discord nutzt Ed25519 (nicht HMAC SHA-256) und liefert zwei Header bei jeder Interaction:

  • X-Signature-Ed25519 — die Signatur in Hex
  • X-Signature-Timestamp — der Timestamp in Sekunden

Du signierst timestamp + rawBody mit dem Public Key, den Discord dir im Developer-Portal zeigt. Die meisten Sprachen haben dafür eine Bibliothek. In Node:

import nacl from 'tweetnacl';

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

Ursache Nummer eins für fehlgeschlagene Prüfungen ist — wieder — Middleware, die den Body parst, bevor dieser Code läuft. Du brauchst den rohen String, den Discord geschickt hat, nicht eine neu serialisierte Version des geparsten Objekts.

Schritt für Schritt: Interaction-Endpoint auf localhost

  1. Starte den HTTP-Server deines Bots lokal (beliebiger Port).
  2. Führe npx portpreview 3000 aus, um eine öffentliche HTTPS-URL zu erhalten.
  3. Öffne im Discord-Developer-Portal deine Anwendung und füge die Tunnel-URL samt Interactions-Pfad in Interactions Endpoint URL ein.
  4. Klicke Save. Discord feuert sofort ein PING. Funktioniert deine Ed25519-Prüfung, wird die URL gespeichert. Sonst zeigt das Feld einen Fehler.
  5. Ist sie gespeichert, führe einen Slash-Command in einem Server aus, in dem dein Bot ist. Die Anfrage landet in deinem Handler.

Schlägt das Speichern wiederholt fehl, inspiziere die erfasste Anfrage — Header, Body und die Antwort deines Servers. In neun von zehn Fällen wurde der Body still von einer JSON-Middleware vor der Prüfung verändert.

Drei-Sekunden-Frist

Discord verlangt eine Antwort in unter drei Sekunden, sonst gilt die Interaction als fehlgeschlagen. Für alles Längere schicke eine erste Typ-5-Antwort (deferred) und PATCHe die ursprüngliche Interaction später mit der echten Antwort. Lokales Testen macht offensichtlich, welche Handler zu langsam sind — die Request-Erfassung zeigt dir die exakte Latenz.

Ausgehende Webhooks sind weiterhin nützlich zum Testen

Willst du nur Nachrichten aus lokalem Code an einen Channel senden, funktioniert die ausgehende Webhook-URL ohne Tunnel. JSON POSTen, Nachricht erscheint. Wir nutzen das für Log-Alerts aus lokalen Dev-Läufen.

Wann du diesem Setup entwächst

Für einen einzelnen Bot mit einer Handvoll Befehle reicht lokales Testen mit Tunnel völlig. Pflegst du einen öffentlichen Bot für Millionen Nutzer, willst du irgendwann ein Staging-Deployment mit stabiler URL, damit Discord nicht jedes Mal neu prüfen muss, wenn die Tunnel-Session rotiert.

Discords Drei-Sekunden-Timeout ist dieselbe Problemfamilie wie GitHubs Zehn-Sekunden-Fenster. Lies unsere Sicht auf Webhook-Retries und Idempotenz für die ausführliche Version. Tritt der PortPreview-Warteliste bei für Tunnel + Erfassung in einer CLI.

Häufig gestellte Fragen

Kann ich Discord-Slash-Commands auf localhost testen?
Nicht direkt. Discord verlangt HTTPS, Ed25519-Signaturprüfung und ein Drei-Sekunden-Antwortfenster. Nutze einen Tunnel, um deinen lokalen Server über HTTPS bereitzustellen, und konfiguriere ihn als Interactions-Endpoint im Developer-Portal.
Warum lehnt Discord meine Interactions-Endpoint-URL ab?
Beim Speichern feuert Discord sofort eine PING-Interaction. Schlägt deine Ed25519-Prüfung fehl oder antwortet dein Handler nicht mit einem gültigen PONG, wird das Speichern abgelehnt. Häufigste Ursache ist Middleware, die den Request-Body vor der Prüfung parst.
Brauchen ausgehende Discord-Webhooks einen Tunnel?
Nein. Ausgehende Webhooks sind URLs, an die du aus deinem Code POSTest, der Traffic geht also nur nach außen. Nur Interaction-Endpoints (die POSTs von Discord empfangen) brauchen einen Tunnel.