SolidHooksSolidHooks
Loslegen
AnwendungsfälleAn mehrere Ziele weiterleiten

Leite einen Webhook an jeden Dienst weiter, der ihn braucht

Stripe sendet ein Rechnungs-Event einmal. Dein Abrechnungsdienst braucht es, dein Data Warehouse möchte eine Kopie, und der Agent, der Hauptbücher abgleicht, möchte auch eine. SolidHooks nimmt diese Anfrage einmal an, speichert den rohen Body und verteilt sie dann über so viele Verbindungen, wie du festgelegt hast. Jede behält ihren eigenen Filter, ihren eigenen Versuchszähler und ihren eigenen Beleg.
Eine eingehende AnfrageUnabhängige Wiederholungen pro ZielAbgerechnet pro Zustellung
Was dieser Leitfaden abdeckt
  1. 01Wie Quellen, Ziele und Verbindungen zusammenpassen
  2. 02Filtern, welche Events welches Ziel erreichen
  3. 03Was Fan-out für Wiederholungen, Durchsatz und deine Rechnung bedeutet
  4. 04Ein Ziel wiederherstellen, ohne die anderen zu stören
  5. 05Die Warteschlange lesen, während sie abgearbeitet wird

Drei Objekte, und nur drei

Eine Quelle ist ein eingehender Endpunkt. Sie hat eine maskId und eine ingestUrl, und diese URL ist die einzige Zugangsberechtigung, die dein Anbieter braucht. Ein Ziel ist ein Ort, an den Events gesendet werden sollen, mit eigenen Headern und eigenem Ratenlimit. Eine Verbindung verknüpft eine Quelle mit einem Ziel und entscheidet, welche Events den Weg nehmen.

Fan-out ist das, was du bekommst, wenn eine Quelle mehr als eine Verbindung hat. An der Quelle ändert sich nichts, und der Anbieter erfährt nie davon.

Provider
POST once
Source
verified, stored
Connections
3 matched
Deliveries
3 billed
Der Anbieter bekommt eine Antwort. Alles nach der Quelle passiert auf unserer Seite, sodass ein langsames Ziel deinen Anbieter nicht zu Wiederholungen bringen kann.

Ein zweites Ziel anbinden

Vorausgesetzt, eine Quelle existiert schon, ist ein weiterer Consumer genau ein Ziel und eine Verbindung. Keins davon berührt den Anbieter.

  1. 1

    Erstelle das Ziel

    POST /v1/destinations mit der URL, die aufgerufen werden soll. Eigene Header werden unterstützt, und die als sensibel markierten werden verschlüsselt gespeichert und nie von der API zurückgegeben.

  2. 2

    Verbinde es mit der Quelle

    Eine Verbindung benennt das Paar und schränkt optional ein, was hindurchfließt. Lass filterRules.eventTypes weg, um alles weiterzuleiten.

    curl
    curl -s "$API/v1/connections" \  -H "authorization: Bearer $SDHK_KEY" \  -H 'content-type: application/json' \  -d '{    "name": "Stripe → billing service",    "sourceId": "src_...",    "destinationId": "dest_...",    "filterRules": { "eventTypes": ["invoice.payment_succeeded"] }  }'
  3. 3

    Sende ein Test-Event

    Sende an die Ingest-URL der Quelle. Eine Quelle mit konfigurierter Signaturprüfung braucht eine gültige Signatur. Ohne sie wird die Anfrage mit 401 beantwortet und mit dem Status rejected gespeichert. Sobald ein Event ankommt, solltest du zwei Zustellungen dazu sehen, eine pro Verbindung, jede mit eigenem Status.

Filtern ohne Regelsprache

Eine Verbindung gleicht auf den Event-Typ ab und auf nichts anderes. Der Typ kommt aus dem eventTypeField der Quelle, einem Punkt-Pfad in die Anfrage. Bekannte Anbieter setzen einen sinnvollen Standard, sodass eine GitHub-Quelle headers.x-github-event liest und eine Stripe-Quelle den Typ aus dem Body, ohne dass du etwas konfigurierst.

Diese eine Einstellung deckt mehr ab, als es scheint, weil du den Pfad wählst. Richte eine Quelle auf body.action, und ihre Verbindungen filtern nach der Aktion statt nach dem Header.

curl
# A GitHub source filters on the event header by default.# Point eventTypeField at a body path to filter on something else:curl -s -X PATCH "$API/v1/sources/src_..." \  -H "authorization: Bearer $SDHK_KEY" \  -H 'content-type: application/json' \  -d '{ "data": { "eventTypeField": "body.action" } }' # Connections on that source now match on the action, not the header:#   "filterRules": { "eventTypes": ["review_requested"] }
Gut zu wissen
Ein Event, das jeder Filter ausschließt, wird trotzdem gespeichert, mit dem Status filtered. Du hast es nicht verloren. Füge die fehlende Verbindung hinzu und spiele es erneut ab.

Was Fan-out kostet

Die abgerechnete Einheit ist die Zustellung, also ein Event, gesendet an ein verbundenes Ziel. Drei Verbindungen an einem Event sind drei Zustellungen. Der Preis folgt der Arbeit statt der Anzahl der Plätze. Ein vierter Consumer kostet also gleich viel, egal ob eine Person oder vierzig ins Dashboard schauen.

Event × VerbindungJe eine abgerechnete Zustellung
automatische WiederholungKostenlos. Dieselbe Zustellung, ein weiterer Versuch.
manuelle WiederholungKostenlos. Es wird keine neue Zustellung erzeugt.
Event erneut abspielenWird abgerechnet. Es erzeugt neue Zustellungen an jeder passenden Verbindung.
doppelte AnfrageNichts abzurechnen. Eine deduplizierte Anfrage wird mit 200 beantwortet und erreicht nie das Fan-out.

Wenn ein Ziel ausfällt

Zustellungen schlagen unabhängig voneinander fehl. Jede wird auf einer Backoff-Kurve wiederholt, die bei 5 Sekunden beginnt und sich bis auf 10 Stunden zwischen den Versuchen öffnet: drei Versuche innerhalb einer Obergrenze von einer Stunde bei Hobby, acht innerhalb einer Obergrenze von 36 Stunden bei bezahlten Plänen, die die Kurve nach etwa 28 Stunden erreicht. Danach wartet sie auf dich, statt zu verschwinden. Die anderen Verbindungen desselben Events sind nicht betroffen und waren schon erfolgreich.

Nicht jede Antwort außerhalb von 2xx zählt als Fehler, und das ist wichtig, wenn du um 3 Uhr nachts ein Log der Versuche liest.

2xxErfolg.
3xxFehler. Weiterleitungen werden nie verfolgt, richte das Ziel also auf seine endgültige URL.
410 GoneZwei aufeinanderfolgende 410 deaktivieren das Ziel und benachrichtigen deine Inhaber und Admins per E-Mail. Jede andere Antwort dazwischen setzt den Zähler zurück, und ebenso das Bearbeiten des Ziels, denn ein Fehlschlag zählt für die Konfiguration, die ihn verursacht hat.
429, 502, 504Geparkt statt fehlgeschlagen. Beachtet Retry-After bis zu einer Stunde und kostet keinen Versuch. Höchstens fünfmal pro Zustellung, und schon früher abgelehnt, wenn die Wartezeit über die Wiederholungsobergrenze deines Plans hinausginge. In beiden Fällen geht es in den normalen Fehlerpfad über.

Es gibt zwei Wege zur Wiederherstellung, und sie sind nicht austauschbar. POST /v1/deliveries/{id}/retry sendet eine Zustellung erneut an ein Ziel und erzeugt nichts Neues, ist also kostenlos. POST /v1/events/{id}/replay schickt das Event noch einmal durch seine Verbindungen, erzeugt neue Zustellungen und wird abgerechnet. Nimm die Wiederholung, wenn ein einzelner Consumer ausgefallen ist, und das erneute Abspielen, wenn du nachträglich eine Verbindung hinzugefügt hast.

Abgelehnte Events lassen sich nicht erneut abspielen
Ein rejected-Event hat die Signaturprüfung nicht bestanden. Es erneut abzuspielen würde also unverifizierte Inhalte unter dem Namen dieser Quelle an deine Ziele schicken. Es liefert 400. Eine als Duplikat verworfene Anfrage wird gar nicht gespeichert, auch da gibt es also nichts erneut abzuspielen.

Taktung ist kein Fehler

Auf der ausgehenden Seite gibt es zwei Ratenlimits: die Zustellungen pro Sekunde deines Plans und das Limit, das du an einem einzelnen Ziel setzt. Wird eines davon überschritten, wird die Zustellung eingereiht, statt fehlzuschlagen. Ihr Status lautet throttled mit dem throttledReason rate_limit, es wird kein Wiederholungsversuch verbraucht, und sie wird gesendet, sobald Kapazität frei wird. Derselbe Status mit destination_busy ist die andere Art von Pause: Das Ziel hat sie mit 429, 502 oder 504 verlangt.

Das zählt vor allem, während sich ein Ziel erholt. Ein Dienst, der gerade wieder da ist, bekommt nicht den ganzen Rückstau auf einmal, und du wirst nicht für eine Warteschlange alarmiert, die korrekt abgearbeitet wird. Eine Zustellung, die je angehalten wurde, behält einen throttledAt-Zeitstempel, auch nachdem sie erfolgreich war, sodass die Pause hinterher sichtbar bleibt.

curl
# Everything that was excluded by a filtercurl -s "$API/v1/events?status=filtered" -H "authorization: Bearer $SDHK_KEY" # What is waiting in the outbound queue right nowcurl -s "$API/v1/deliveries/stats" -H "authorization: Bearer $SDHK_KEY"→ { "pending": 4, "throttled": 412 }

Bewährte Praktiken

  • Gib jedem Consumer seine eigene Verbindung, statt eine zu teilen und in deinem Handler zu verzweigen. Getrennte Verbindungen bringen dir getrennten Wiederholungsstatus und getrennte Belege.
  • Setze ein Ratenlimit an jedem Ziel, von dem du weißt, dass es empfindlich ist. Einreihen ist für beide Seiten günstiger als eine Welle von Fehlern mit anschließendem Backoff.
  • Filtere an der Verbindung, nicht in deinem Handler. Ein Event, das dein Dienst ohnehin verwerfen würde, kostet trotzdem eine Zustellung, wenn du es durchlässt.
  • Prüfe ?status=filtered, wenn ein Consumer fehlende Events meldet. Die übliche Ursache ist ein eventTypeField, das woandershin zeigt, als du dachtest.
  • Mach Handler idempotent. Erneutes Abspielen gibt es, Leute nutzen es während Incidents, und dein Endpunkt sollte eine zweite Kopie als No-op behandeln.

Fragen

Verteile eine Quelle an alles, was sie braucht

Erstelle eine Quelle, füge so viele Verbindungen hinzu, wie du Consumer hast, und sieh zu, wie sich jede einpendelt.