Startseite / Artikel

Im Inneren eines Mehr-Anbieter-Benachrichtigungsdienstes: Warteschlangen, Rückfälle, Vertrauen

Eine detaillierte Darstellung eines zuverlässigen Benachrichtigungsdienstes: Speicherung von Anmeldeinformationen, Fallback-Möglichkeiten bei Anbietern, Wiederholungsversuche mit BullMQ und DLQs, Gerechtigkeit gegenüber verschiedenen Nutzern sowie Überprüfungen der Webhook-Signatur.

Verlag: REACTAPP.TOP12 Min. Lesezeit2659 Wörter

Quellen: Building NotifyKit: The Engineering Behind Reliable Notifications

Das Versenden einer E-Mail oder SMS scheint eine einfache Aufgabe zu sein: Man ruft die API des Anbieters auf und ist fertig. In der Praxis wird dieser einzelne send()-Aufruf jedoch allmählich zu einem Problem für verteilte Systeme, da Anbieter Zeitlimits setzen, die Übertragungsraten begrenzen, offline gehen, die Nachrichten in falscher Reihenfolge liefern und Wiederholungsversuche Duplikate erzeugen können. Dieser Artikel analysiert die Architektur eines open-source-basierten, multi-tenantigen Benachrichtigungsdienstes, der auf Node.js, BullMQ und Redis aufbaut, und behandelt dabei sieben Bereiche: Geheime Informationen, Ausfälle der Anbieter, Queue-Design, Reihenfolge der Zustellung, Lieferfähigkeit, Gerechtigkeit gegenüber verschiedenen Nutzern sowie Verifizierung von Webhooks. Für jeden dieser Bereiche werden die getroffenen Entscheidungen, die dahinterstehenden Gründe sowie die bewusst in Kauf genommenen Einschränkungen erläutert, damit Sie diese Überlegungen in Ihren eigenen Systemen wiederverwenden können.

Zwei Arten von Geheiminformationen, zwei Speicherrstrategien

Eine Benachrichtigungsplattform besitzt mehrere Anmeldeinformationen, und die entscheidende Frage für jede ist einfach: Braucht das System jemals den ursprünglichen Wert wieder?

Anbieter-Schlüssel: Verschlüsseln, weil sie wiederhergestellt werden müssen

Kunden bringen ihre eigenen API-Schlüssel des E-Mail-Anbieters mit, und die Plattform muss diese Schlüssel bei jeder Übermittlung beim Anbieter vorlegen. Das schließt das Hashen aus; der Wert muss wiederherstellbar sein. Bei der Gestaltung wird zunächst jeder Schlüssel gegen seinen jeweiligen Anbieter überprüft, anschließend mit AES-256-GCM verschlüsselt, bevor er gespeichert wird. GCM ist ein authentifizierter Verschlüsselungsmodus, sodass neben der Vertraulichkeit auch Manipulationen erkannt werden: Ein veränderter Verschlüsseltext lässt sich nicht entschlüsseln, anstatt einen plausibel wirkenden, aber falschen Anmeldecode zu ergeben.

Der Klartext kommt niemals mit der Datenbank, den Protokollen oder dem Cache in Berührung. Er wird an genau zwei Stellen entschlüsselt: um eine maskierte Vorschau im Dashboard anzuzeigen, sowie innerhalb des Sendeworkers unmittelbar vor der Anfrage an den externen Anbieter. Die Beschränkung der Entschlüsselung auf einige gut definierte Codepfade macht eine versehentliche Offenlegung weitaus leichter überprüfbar.

Von der Plattform ausgestellte API-Schlüssel: Hash, da sie nur überprüft werden müssen

Die API-Schlüssel, die die Plattform ihren eigenen Kunden ausstellt, folgen der entgegengesetzten Logik. Das System muss sie niemals wiederherstellen, sondern lediglich sicherstellen, dass ein vorgelegter Schlüssel echt ist. Daher wird der Schlüssel bei seiner Erstellung einmal angezeigt, und nur sein SHA-256-Hash wird gespeichert. Bei jeder Anfrage wird der eingehende Schlüssel gehasht und mit dem gespeicherten Hash verglichen. Ein verlorener Schlüssel kann nicht wiederhergestellt werden und muss neu generiert werden.

Ein schneller Hash wie SHA-256 eignet sich hier, da die Schlüssel lange, zufällige Zeichenketten mit hoher Entropie sind. Die gleiche Wahl wäre für menschliche Passwörter falsch, da diese einen absichtlich langsamen Algorithmus wie bcrypt, scrypt oder Argon2 benötigen, um gegen das Erraten geschützt zu sein.

Geheime Schlüssel zur Signierung von Webhooks: irgendwo dazwischen

Der zur Signierung ausgehender Webhooks verwendete Geheimschlüssel wird von der Plattform erzeugt, ähnlich wie eine API-Schlüssel, doch sein Rohwert wird jedes Mal benötigt, wenn ein Webhook signiert wird – genauso wie bei einem Anbieter-Schlüssel. Er wird daher im Ruhezustand mit AES-256-GCM verschlüsselt, unmittelbar vor jeder Signierung wieder entschlüsselt und während der Nutzung nur im Speicher gehalten.

Umgang mit unzuverlässigen Anbietern durch geordnete Fallback-Möglichkeiten

Jedes System, das an einen einzigen Anbieter gebunden ist, leidet unter den Ausfällen, Leistungsbeschränkungen und Zeitüberschreitungen dieses Anbieters. Der Service verringert diese Auswirkungen mithilfe einer Prioritätenliste pro Kunde. Ein Kunde kann mehrere Anbieter registrieren, beispielsweise SendGrid, Resend und Postmark, und diese rangieren. Wenn in einer Versandanfrage kein Anbieter angegeben wird, wird die Liste der zu prüfenden Anbieter entsprechend dieser Rangliste erstellt, wobei die höchst priorisierten Anbieter zuerst berücksichtigt werden.

Der Versandversuch wird nacheinander an den Anbietern durchgeführt. Wenn ein Anbieter wegen Ausfall, Leistungsbeschränkung oder zu langsamer Reaktionszeit versagt, wird dieser Fehler protokolliert und es wird der nächste Anbieter ausprobiert. Der erste erfolgreiche Versuch beendet den Prozess, und der Kunde bemerkt die Fallback-Mechanik nur dann, wenn alle Anbieter versagen.

Kunden können außerdem eine Nachricht gezielt an einen bestimmten Anbieter senden, optional mit einem einzigen benannten Fallback-Anbieter; in diesem Fall wird nur dieses Paar ausprobiert. Nicht jeder Fehler wird gleich behandelt:

  • Dauerhafte Fehler, wie abgelehnte Anmeldeinformationen oder fehlerhafte Anfragen, führen sofort zum Scheitern, da ein erneuter Versuch sie nicht beheben kann.
  • Vorübergehende Fehler, wie Zeitüberschreitungen, Auflagen bezüglich der Anfragenhäufigkeit sowie 5xx-Antworten des Anbieters, werden mit Verzögerung erneut versucht.

Falls die gesamte Liste fehlschlägt, gelangt der Fehler in die Warteschlange, deren eigene Strategie zur erneuten Verarbeitung greift. Ein Fallback beseitigt die Fehler des Anbieters nicht; er gibt jeder Nachricht mehr Chancen auf Erfolg, bevor ein Fehler für den Kunden sichtbar wird.

Eine Feinheit, die berücksichtigt werden sollte: Eine Zeitüberschreitung beweist nicht, dass der Anbieter die Nachricht abgelehnt hat. Ein Anbieter kann eine Anfrage akzeptieren und anschließend versäumen, rechtzeitig zu antworten; in diesem Fall kann das Zurückgreifen auf eine Alternative zur gleichen Benachrichtigung zweimal ausgeliefert werden. Die üblichen Lösungswege sind das Übergeben einer Idempotenzschlüssel, sofern der Anbieter diese unterstützt, oder das Akzeptieren gelegentlicher Duplikate bei weniger wichtigen Nachrichten. Unser Artikel zu Idempotenzschlüsseln für Node.js POST-Endpunkte behandelt dieses Konzept ausführlich.

Warteschlangenarchitektur

Die Übermittlung einer Benachrichtigung innerhalb der API-Anfrage, die sie ausgelöst hat, verknüpft die Latenz des Aufrufers mit dem langsamsten Anbieter. Ein träge oder nicht verfügbarer Anbieter würde den HTTP-Aufruf des Kunden blockieren, bis dieser schließlich erfolgreich ist oder eine Zeitüberschreitung auftritt. Der Service vermeidet dies, indem er Akzeptanz und Übermittlung voneinander trennt.

Trennung der Anfrage von der Übermittlung

Die API prüft lediglich die Anfrage, erstellt eine Job-Zeile in der Datenbank und stellt die Aufgabe in die Warteschlange. BullMQ auf Redis übernimmt die Planung, wobei für jede Art von Aufgabe eine eigene Warteschlange vorhanden ist:

  • notifications-email für Kunden-E-Mails
  • notifications-webhook für Kunden-Webhooks
  • platform-email für interne Warnungen des Services selbst
  • notifications-failed als Dead-Letter-Queue für Aufgaben, die ihre Wiederholungsversuche aufgebraucht haben

Sonstige Warteschlangen bedeuten, dass eine Art von Aufgabe verlangsamt werden oder angepasst werden kann, ohne die anderen zu beeinflussen. Für eine ausführlichere Behandlung der Job-Design-Methoden bei BullMQ siehe building reliable background job systems with BullMQ and Redis.

Die Anzahl der Wiederholungsversuche bezieht sich auf gesamte Durchläufe, nicht auf einzelne Anbieter

Jede Aufgabe erhält drei Versuche mit exponentiellem Backoff, und der wichtige Punkt ist, was ein „Versuch“ bedeutet. Es handelt sich dabei nicht um einen Versuch pro Anbieter. Ein einziger Versuch bedeutet eine vollständige Abarbeitung der Provider des Kunden in Prioritätsreihenfolge, wobei bei ersterem Erfolg gestoppt wird. Wenn alle Provider in einer Durchgangsabarbeitung fehlschlagen, wurde ein Versuch aufgebraucht, und der nächste Neversuch beginnt erneut mit dem höchstpriorisierten Provider. Erst nach drei fehlgeschlagenen Durchgängen wird die Aufgabe inklusive des Fehlers in die Dead-Letter-Queue kopiert, und die ursprüngliche Aufgabe wird als FAILED markiert.

Die Datenbank bleibt die Quelle der Wahrheit, selbst wenn die Warteschlange selbst ausfällt. Ein Kundenauftrag Job oder ein PlatformEmailLog für interne E-Mails wird mit dem Status PENDING gespeichert, bevor etwas in die Warteschlange eingereiht wird. Wenn Redis nicht erreichbar ist oder das Hinzufügen des Auftrags zu einem Fehler führt, wird dieser erfasst, das Versagen gemeldet und die Zeile auf FAILED mit der entsprechenden Begründung aktualisiert. Kein Antrag verschwindet stillschweigend zwischen API und Warteschlange.

Zweck der Dead-Letter-Warteschlange

Wenn ein Auftrag seine drei Versuche aufgebraucht hat, landet eine Kopie in notifications-failed, die den vollständigen Inhalt, den Fehler sowie ein Zeitstempel enthält, während das Datensatz-Record in der Datenbank auf FAILED mit derselben Begründung gesetzt wird. Die doppelte Erfassung des Fehlers ist beabsichtigt, da jeder Datensatz einem anderen Empfängergruppe dient:

  • Die Datenbankzeile ist für den Kunden bestimmt und unterstützt GET /jobs/:id.
  • Der Eintrag im DLQ ist für die Operator bestimmt und stellt eine einmalige Kopie dar, die zur Überwachung und zum Handeln gedacht ist.

Ein DLQ ist nur dann nützlich, wenn er überwacht wird. Jeder eingehende Job löst eine Slack-Benachrichtigung aus, wobei der bereits für E-Mail-Fehler auf der Plattform erstellte Benachrichtigungsweg wiederverwendet wird. Dadurch wird die Warteschlange zu einem Frühwarnsignal: Ein Anstieg an Jobs im DLQ innerhalb kurzer Zeit weist in der Regel auf ein systemisches Problem hin, wie zum Beispiel einen Ausfall des Anbieters oder eine fehlerhafte Bereitstellung.

Nichts verlässt automatisch den DLQ. Ein fehlerhafter Job wird nur dann erneut versucht, wenn der Kunde POST /jobs/:id/retry aufruft, was ihn auf PENDING zurücksetzt und den Ablauf neu startet.

Priorität und Konkurrenz

Arbeiten haben eine von drei Prioritäten: CRITICAL, NORMAL oder LOW, die mit BullsMQs eingebauter Prioritätsunterstützung abgebildet werden. Der Verkehr im kostenlosen Plan wird immer auf LOW gesetzt, damit er nicht mit dem bezahlten Verkehr konkurrieren kann. Plattform-E-Mails umgehen diese Logik völlig: Sie werden immer als CRITICAL eingereiht und verwenden eine kürzere Wartezeit.

Worker-Pools ziehen kontinuierlich Arbeiten ab, wobei jede Aufgabe mit einer Konkurrenz von fünf gleichzeitig ausgeführt wird, sodass pro Pool bis zu fünf Arbeiten gleichzeitig verarbeitet werden können.

Sortierung: eine bewusste Lücke

Dienstleistung und Service garantieren keine Abwicklung der Aufträge nach dem Kundenprinzip. Konkurrenz und Priorität werden auf die Durchsatzrate abgestimmt, wodurch zwei nacheinander gesendete Nachrichten bei fünf parallelen Verarbeitungskanälen sowie Wiederholungsversuchen leicht in umgekehrter Reihenfolge zugestellt werden können. Für die meisten Benachrichtigungen ist dies unbedenklich, da sie unabhängig voneinander sind. Ein Problem entsteht nur bei kausal miteinander verbundenen Nachrichtenpaaren, bei denen die Reihenfolge eine Bedeutung hat – beispielsweise wenn eine Nachricht logischerweise auf eine andere folgen sollte.

Es gibt zwei allgemeine Lösungsansätze, von denen jeder einen Preis hat:

  • Serialisieren nach Kunden. Leiten Sie die Aufträge eines Kunden über einen einzigen Kanal weiter, um eine geordnete Abwicklung zu gewährleisten – auf Kosten der Parallelverarbeitung für diesen Kunden.
  • Machten Sie die Nachrichten reihenfolgenunempfindlich. Fügen Sie Zeitstempel hinzu und überlassen Sie es den Empfängern, die Reihenfolge selbst zu interpretieren; dadurch wird die Verantwortung auf die Art und Weise verlagert, wie die Kunden die Zustellungen lesen.

Die Beschränkung ist bekannt und wurde zugunsten der Durchsatzleistung in Kauf genommen. Die aktuelle Nutzung rechtfertigt dennoch noch keinen Aufwand, weshalb die Entscheidung aufgeschoben wird, bis tatsächliche Kundendaten zeigen, welcher Kompromiss besser geeignet ist. Eine explizite Dokumentation eines solchen Problems ist oft besser als eine unvollständige Lösung.

Lieferfähigkeit: Fehlervermeidung vor und nach dem Versand

E-Mails können je nach Reputation des Absenders sowie den im Laufe der Zeit angesammelten Signalen zurückgewiesen werden, in den Spam gelangen oder verschwinden. Der Service begegnet diesem Problem an zwei Stellen.

Vor dem Versand: Domainverifizierung

Wenn ein Kunde von seiner eigenen Domain aus senden möchte, registriert der Service diese Domain beim vom Kunden gewählten Anbieter und erhält die DNS-Einträge, die das Eigentum nachweisen. Diese Einträge unterscheiden sich je nach Anbieter, weshalb sie dem Kunden zur Eingabe bei seinem DNS-Host angezeigt werden. Das Senden von dieser Domain bleibt blockiert, bis die Überprüfung erfolgreich abgeschlossen ist.

Nach dem Versand: Reaktion auf Ereignisse des Anbieters

Die Anbieter melden Lieferungen, Öffnungen, Klicks, Abpraller sowie Spam-Beschwerden über Webhooks. Der Service speichert diese Ereignisse und reagiert entsprechend der Art:

  • Ein harter Abpraller (die Adresse existiert nicht oder wird dauerhaft abgelehnt) führt automatisch zur Unterdrückung des Empfängers, und spätere Versuche an diese Adresse werden blockiert, bevor ein Anbieter kontaktiert wird.
  • Eine Spam-Beschwerde löst dieselbe Unterdrückung aus.
  • Ein Soft Bounce, wie beispielsweise ein voller Posteingang oder ein vorübergehend unzugänglicher Server, wird nur erfasst, weil das Problem vermutlich vorübergehend ist.
  • Die Regel lautet, dass nur Signale, die auf ein dauerhaftes Problem hindeuten, zu einer Blockierung führen. Dies löst das Problem der Zustellbarkeit nicht vollständig, da der Rufwert je nach Faktoren außerhalb einer einzelnen Nachricht steigt oder fällt, doch es verhindert, dass das System immer wieder Adressen anfährt, bei denen bekanntermaßen Fehler auftreten – was wiederum einer der Faktoren ist, die den Rufwert schädigen.

    Verhindern, dass die Nutzer sich gegenseitig schaden

    Sämtliche Kunden teilen sich dieselben Warteschlangen, Arbeitspool sowie die Redis-Instanz; nichts ist physisch isoliert. Die Fairness ergibt sich aus drei Kontrollmechanismen, die in verschiedenen Phasen angewandt werden.

    Erste Hürde: Rate Limits pro Kunde an der API

    Bevor eine Anfrage zu einer Aufgabe wird, wird sie innerhalb eines Zeitraums von 60 Sekunden hinsichtlich der pro Kunde festgelegten Rate-Limit-Regeln, die auf Redis basieren, überprüft – deren Größe hängt vom Plan ab:

    • FREE: 5 Anfragen pro Minute
    • INDIE: 50 Anfragen pro Minute
    • STARTUP: 200 Anfragen pro Minute

    Ein plötzlicher Anstieg der Anfragen von einem Konto wird bereits an der Grenze abgewiesen und führt nicht dazu, dass die Warteschlange überflutet wird.

    Zweite Hürde: Priorität nach der Annahme

    Aufgenommene Aufgaben werden nach Priorität verarbeitet, wobei Aufgaben des kostenlosen Plans stets die LOW-Priorität haben. Die Worker bearbeiten immer zuerst Aufgaben mit höherer Priorität, sodass selbst bei einem Rückstand bei kostenlosen Konten die Benachrichtigungen von bezahlten Kunden vorangebracht werden.

    Dritte Hürde: Monatliche Quoten

    Über einen längeren Zeitraum hinweg begrenzen Quoten die Menge der Anfragen. Der kostenlose Plan erlaubt 100 gesamte Versendungen von E-Mails und Webhooks pro Monat. Bei den bezahlten Plänen gilt eine Begrenzung nur für Webhooks; E-Mails sind unbegrenzt.

    Was noch fehlt

    Es gibt keine strenge Ressourcenisolation: Es gibt keine dedizierten Prozesse pro Kunde. Zwei Kunden derselben Prioritätsstufe teilen sich die gleiche Kapazität, und innerhalb einer Stufe werden Aufgaben in der Regel nach dem Prinzip „Wer zuerst kommt, mahlt zuerst“ bearbeitet. Bei dem derzeitigen Volumen war das kein Problem, da der Verkehr problemlos in der Leistungskapazität der gemeinsamen Infrastruktur liegt und kein Kunde den anderen nennenswert verlangsamt hat. Bei größeren Mengen würde eine Isolation pro Kunde oder ein fairer Scheduling-Plan (zum Beispiel Round-Robin unter den Kunden derselben Prioritätsstufe) notwendig werden.

    Überprüfung von Webhooks in beide Richtungen

    Jeder integrierte Anbieter sendet Ereignisse über Webhooks zurück: Zustellbestätigungen, Abstoßmeldungen, Spam-Meldungen sowie Änderungen des Zahlungszustands. Jeder Empfangspunkt ist eine öffentliche URL. Ohne Überprüfung des Senders könnte jeder ein falsches „zustelltes“ Ereignis posten, eine Abstoßmeldung fälschen, um die Adresse einer anderen Person zu unterdrücken, oder – was am schädlichsten ist – ein falsches Zahlungsereignis erzeugen, um ein Abo als bezahlt zu markieren.

    Eingehende Kommunikation: ein Schema pro Anbieter

    Es gibt keinen gemeinsamen Standard, weshalb jede Integration eine eigene Sicherheitsmaßnahme darstellt:

    • SendGrid signiert die Payloads mit ECDSA, und die Signatur wird vor der Vertrauensgewährung gegenüber dem öffentlichen Schlüssel von SendGrid überprüft.
    • Resend liefert Webhooks über Svix, das ein mit Zeitstempel versehenes HMAC-Verfahren verwendet, das sowohl vor Manipulationen als auch vor Wiederholungssendungen schützt.
  • Postmark signiert Webhooks nicht, weshalb sein Endpunkt auf HTTP Basic Auth mit einem pro-Kunde gespeicherten Geheimnis setzt.
  • Paystack-Zahlungs-Webhooks werden mithilfe von HMAC unter Verwendung von SHA-512 überprüft.
  • Polar-Zahlungs-Webhooks werden mit der standardwebhooks-Bibliothek validiert.
  • Im Fall von Postmark gibt es eine leicht übersehbare Falle: Das Geheimnis muss in konstanter Zeit verglichen werden, beispielsweise mit Node’s crypto.timingSafeEqual, und nicht mit einer normalen Zeichenketten-Vergleichsoperation. Ein gewöhnlicher Vergleich kann bereits bei der ersten abweichenden Zeichen zurückgegeben werden, und diese Zeitunterschiede können es einem Angreifer ermöglichen, das Geheimnis Zeichen für Zeichen wiederzuerlangen, anstatt den gesamten Wert per Brute-Force zu ermitteln.

    Absolut keine dieser Schemata sind austauschbar, und jede Integration muss von Grund auf erlernt und umgesetzt werden. Die für alle geltende Konstante ist, dass nichts, was an einen webhook-Endpunkt gelangt, bearbeitet wird, bevor seine Herkunft überprüft wurde.

    Ausgehende Kommunikation: Kundenvertrauen in die Plattform stärken

    Das gleiche Problem tritt umgekehrt auf, wenn der Dienst Webhooks an die Endpunkte der Kunden übermittelt. Wenn ein Kunde ein Signiergeheimnis konfiguriert hat, enthält jede Übertragung einen X-Webhook-Timestamp-Header sowie einen X-Webhook-Signature-Header. Die Signatur ist ein HMAC-SHA256-Wert, der über den String timestamp.payload berechnet wird und den Inhalt mit einem bestimmten Zeitpunkt verknüpft. Der Empfänger rechnet die Signatur erneut aus und lehnt außerdem zu alte Zeitstempel ab, wodurch verhindert wird, dass eine abgefangene Anfrage später erneut gesendet werden kann.

    Kunden haben die volle Kontrolle über ihr Geheimnis: Sie können es erzeugen, seinen Status überprüfen, es ändern oder löschen. Ohne ein konfiguriertes Geheimnis werden Webhooks dennoch übermittelt, allerdings unsigniert. Das fördert die Rückwärtskompatibilität, bedeutet aber auch, dass Empfänger sich aktiv für die Verifizierung entscheiden müssen, um geschützt zu sein. Für eine ergänzende Sichtweise vom Empfängerseite siehe Verständnis von Idempotenzschlüsseln in Node.js-POST-Endpunkten.

    Ein kleiner Fallback, dann die Signaturprüfung

    deliver geht die Anbieter der Reihe nach durch und gibt den ersten zurück, der die Nachricht annimmt. Ein erneuter Versuch des ganzen Jobs gehört in die Queue.

    type Provider = {
      name: string;
      send: (to: string, body: string) => Promise<void>;
    };
    
    const deliver = async (providers: Provider[], to: string, body: string) => {
      const errors: string[] = [];
      for (const provider of providers) {
        try {
          await provider.send(to, body);
          return provider.name;
        } catch (err) {
          errors.push(`${provider.name}: ${(err as Error).message}`);
        }
      }
      throw new Error(`all providers failed\n${errors.join('\n')}`);
    };

    Die Signatur deckt timestamp.payload ab. Der Empfänger lehnt einen falschen MAC und einen Zeitstempel ab, der älter als fünf Minuten ist.

    import { createHmac, timingSafeEqual } from 'node:crypto';
    
    const sign = (secret: string, timestamp: string, payload: string) =>
      createHmac('sha256', secret).update(`${timestamp}.${payload}`).digest('hex');
    
    const verify = (
      secret: string,
      timestamp: string,
      payload: string,
      signature: string,
    ) => {
      const ageMs = Math.abs(Date.now() - Number(timestamp));
      if (!Number.isFinite(ageMs) || ageMs > 5 * 60 * 1000) return false;
      const expected = Buffer.from(sign(secret, timestamp, payload));
      const received = Buffer.from(signature);
      if (expected.length !== received.length) return false;
      return timingSafeEqual(expected, received);
    };

    Haupterkenntnisse

    • Entscheiden Sie zwischen Verschlüsselung und Hashing, indem Sie prüfen, ob der ursprüngliche Wert jemals wiederhergestellt werden muss.
    • Betrachten Sie den Provider-Fallback sowie Wiederholungsversuche in der Warteschlange als getrennte Ebenen und definieren Sie genau, was eine „Versuch“ bedeutet.
    • Schreiben Sie vor dem Hinzufügen in die Warteschlange einen dauerhaften Datenbankeintrag, damit ein Ausfall der Warteschlange keine Anfrage verliert.
  • Eine Dead-Letter-Queue ist nur dann nützlich, wenn darauf Benachrichtigungen ausgelöst werden und ein klarer Weg für erneute Versuche existiert.
  • Unterdrücken Sie die Benachrichtigungen nur bei dauerhaften Signalen wie harten Bounces und Spam-Beschwerden.
  • Setzen Sie Kontrollmechanismen zur Gleichbehandlung ein (Durchsatzbeschränkungen, Prioritäten, Quoten) und machen Sie klar, welche Isolierungsfunktionen noch nicht bereitgestellt werden.
  • Überprüfen Sie jeden eingehenden webhook mit dem eigenen Schema des Anbieters, verwenden Sie Vergleiche in konstanter Zeit und signieren Sie ausgehende webhooks mit einem Zeitstempel, um Wiederholungen zu verhindern.
  • Nennen Sie Ihre bekannten Einschränkungen, wie z. B. die Reihenfolge der Verarbeitung, und verschieben Sie teure Korrekturen, bis die Daten zeigen, welche Kompromisse sinnvoll sind.