Zum Inhalt springen
Doku

Fehlerbehebung

Warum stimmt meine Webhook-Signatur nicht überein?

Beheben Sie Fehler bei der Verifizierung von Webhook-Signaturen. Signieren Sie den unveränderten Body mit dem vollständigen whsec_-Secret und dem Wert von X-Emailit-Timestamp.

Aktualisiert am 1. Okt. 2026

Dieser Artikel hilft, wenn Ihr Endpunkt Webhooks von Emailit ablehnt, weil die von Ihnen berechnete Signatur nicht dem Header X-Emailit-Signature entspricht. Fast immer unterscheiden sich die Bytes, die Sie signieren, oder das Secret, das Sie verwenden, von dem, was Emailit verwendet hat.

Symptome

  • Ihr Handler gibt für jeden Webhook 401 oder 400 mit „invalid signature“ zurück, auch für Events von Send test aus der Weboberfläche.
  • Anfragen auf dem Tab Requests des Webhooks zeigen fehlgeschlagene Versuche mit dem Fehlerstatuscode Ihres Endpunkts.
  • Die Verifizierung funktioniert in einem Unit-Test mit einem selbst erstellten Payload, schlägt aber bei echten Zustellungen fehl.

Ursache

Emailit signiert jede Anfrage so:

Text
signature = hex( HMAC-SHA256( secret, "<X-Emailit-Timestamp>.<raw request body>" ) )

Die Signatur schlägt fehl, wenn sich eine der drei Eingaben unterscheidet:

  • Der Body wurde neu serialisiert. Frameworks parsen JSON oft, bevor Ihr Handler läuft. JSON.stringify(req.body) erzeugt andere Leerzeichen und eine andere Reihenfolge der Schlüssel als die Bytes, die Emailit gesendet hat. Der Body ist ein JSON-Array mit bis zu 100 Events, daher kann auch Code, der ein Objekt erwartet, ihn umformen.
  • Das Secret ist falsch. Verwenden Sie den vollständigen Wert einschließlich des Präfixes whsec_. Jeder Webhook hat ein eigenes Secret, und das Zurücksetzen macht das alte sofort ungültig, auch für ausstehende Wiederholungen.
  • Der Zeitstempel fehlt oder weicht ab. Verwenden Sie den genauen Wert des Headers X-Emailit-Timestamp und verbinden Sie ihn mit einem einzelnen Punkt mit dem Body.
  • Der Vergleich ist falsch. Der Header ist hexadezimal in Kleinbuchstaben kodiert, nicht Base64, und hat kein Präfix sha256=.
  • Etwas vor Ihrer App hat den Body verändert, etwa ein Proxy, der ihn neu kodiert oder dekomprimiert.

Lösung

  1. Unveränderten Body lesen. Erfassen Sie die Bytes, bevor ein JSON-Parser läuft. Binden Sie in Express express.raw() nur für die Webhook-Route ein:

    webhook.js
    import crypto from 'node:crypto';
    
    app.post('/webhooks/emailit', express.raw({ type: 'application/json' }), (req, res) => {
      const timestamp = req.get('X-Emailit-Timestamp');
      const expected = crypto
        .createHmac('sha256', process.env.EMAILIT_WEBHOOK_SECRET) // whsec_...
        .update(`${timestamp}.${req.body.toString('utf8')}`)
        .digest('hex');
      const received = req.get('X-Emailit-Signature') ?? '';
      const valid = received.length === expected.length &&
        crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
      if (!valid) return res.sendStatus(401);
    
      const events = JSON.parse(req.body); // an array of events
      res.sendStatus(200);
    });

    In Route-Handlern von Next.js verwenden Sie await request.text(), in Laravel $request->getContent() und in Flask request.get_data().

  2. Richtiges Secret beschaffen. Rufen Sie Webhook abrufen auf, das das aktuelle secret zurückgibt. In der Weboberfläche wird das Secret nur einmal angezeigt. Wenn Sie es verloren haben, öffnen Sie den Webhook, setzen Sie es mit Webhook secret zurück und aktualisieren Sie sofort Ihre Umgebungsvariable.

  3. Test-Event senden. Wählen Sie auf der Seite des Webhooks Send test und einen Event-Typ. Der Test wird mit demselben Secret signiert, und der Dialog zeigt den Statuscode und den Antwort-Body Ihres Endpunkts.

  4. Toleranz für die Uhrzeit prüfen. Wenn Sie alte Zeitstempel ablehnen, um Replay-Angriffe zu verhindern, lassen Sie einige Minuten Spielraum und halten Sie die Uhr Ihres Servers synchron. Jeder Zustellversuch, auch jede Wiederholung, trägt einen neuen Zeitstempel.

  5. Fehlgeschlagenes wiederholen. Sobald die Verifizierung funktioniert, wählen Sie Retry failed, um fehlgeschlagene Zustellungen der letzten 7 Tage erneut zu senden.

Code in anderen Sprachen finden Sie unter Signatur von Webhook-Anfragen, das Format des Bodys unter Webhook-Anfragen.

Weiterhin Probleme?

Kontaktieren Sie den Support oder fragen Sie auf Discord. Teilen Sie die Webhook-ID (wh_…) und die ID eines fehlschlagenden Events mit, nie das Secret.

War diese Seite hilfreich?

Danke für Ihr Feedback.

Danke, wir lesen jede Nachricht.