Das bekommen Sie
Ein vollständiger Service, den Sie an einem Nachmittag durchlesen können, kein Framework. Ihre Logik gehört in eine Funktion, handle(). Alles drumherum ist fertig und getestet.
sender --POST--> /webhook/{source}
1. read the body (size limit)
2. verify the HMAC signature -> 401 if wrong, nothing stored
3. dedupe by delivery id -> the same event twice is handled once
4. insert into SQLite -> 202 Accepted, fast
worker ----------> handle(event) retries with backoff, dead after N attempts
Funktionen
- Drei Signaturverfahren. GitHub-Stil, Stripe-Stil (mit Replay-Fenster) und Standard Webhooks, das Verfahren von Polar. Der Vergleich läuft in konstanter Zeit.
- Secret-Rotation.
WEBHOOK_SECRETnimmt eine kommagetrennte Liste. Während einer Rotation gelten altes und neues Secret, und kein Event geht verloren. - Deduplizierung. Über die Delivery-ID (bei Standard Webhooks wird automatisch der Header
webhook-idverwendet) oder, falls es keine gibt, über einen Hash des Bodys. Eine Unique-Constraint sichert das ab, die Deduplizierung übersteht also Neustarts. - Dauerhafte Queue. SQLite im WAL-Modus. Die Anfrage wird nach einem einzigen Commit bestätigt. Dazu kommen exponentielles Backoff, eine Dead-Letter-Ablage nach
MAX_ATTEMPTSund die Wiederaufnahme von Events, die ein Absturz im Statusprocessingzurückgelassen hat. - Parallelität.
WORKERS=NWorker. Ein Event geht nie an zwei Worker zugleich. - Betrieb.
server replayfür Events im Dead-Letter-Status,server purgeundRETENTION_DAYSfür alte Zeilen,/metricsim Prometheus-Format und ein JSON-Access-Log mit Request-IDs, das weder Payloads noch Header oder Secrets enthält. - Sichere Handler.
worker.Permanent(err)legt eine Payload ab, die nie erfolgreich sein kann, ohne Retries zu verbrennen. Ein Shutdown mitten im Handler stellt das Event zurück in die Queue, ohne dass ein Versuch verloren geht. - Beispiele. Ein Handler für
order.paidvon Polar und ein generischer JSON-Router, beide mit Tests. Dockerfile und systemd-Unit liegen bei.
Pures Go (ohne cgo). Abhängigkeiten: die Standardbibliothek und modernc.org/sqlite.
Gemessen
Methode: der mitgelieferte Lasttester (make loadtest) gegen das kompilierte Binary auf einem Apple-Silicon-Laptop, 32 Verbindungen, Payloads mit 512 Byte, echtes HTTP über Loopback, Standard-Durability (WAL, synchronous=FULL), 5,000 Events pro Lauf, drei Läufe.
| Messung | Ergebnis |
|---|---|
| Per HTTP angenommene Events (Signatur prüfen, dauerhaft speichern, 202), GitHub-Stil | etwa 7,200 bis 9,200 pro Sekunde (p50 2.7 ms, p99 etwa 17 ms) |
| Dasselbe mit Standard Webhooks | etwa 9,400 pro Sekunde |
| Ende zu Ende, vom ersten Request bis zur leeren Queue (Handler ohne Arbeit), 1 Worker | etwa 3,100 bis 3,200 pro Sekunde |
| Queue leeren, Handler mit 5 ms Wartezeit (wie ein Netzwerkaufruf), 1 / 8 / 32 Worker | etwa 163 / 1,463 / 5,264 pro Sekunde |
Den Hauptteil der Kosten verursacht die Datenbank, nicht HTTP. Der gesamte SQLite-Zugriff läuft über eine einzige Verbindung. Mehr Worker lohnen sich daher, wenn Ihr Handler auf etwas wartet, etwa auf einen Netzwerkaufruf oder eine andere Datenbank.
So wurde getestet
make test führt go vet und 41 Tests mit dem Race Detector aus. Sie decken ab: manipulierte, abgelaufene, aus der Zukunft stammende und fehlerhaft aufgebaute Signaturen, den Known-Answer-Vektor von Standard Webhooks aus der Referenzbibliothek in Go, Secret-Rotation, 401 ohne Speichern, doppelte Zustellungen, zu große Requests und falsche HTTP-Methoden, Retries und Backoff, Dead-Letter, Wiederaufnahme nach Absturz, Reihenfolge, viele Worker ohne doppelt verarbeitetes Event, Replay, Purge und das Upgrade einer 0.1.0-Datenbank. Das aus dem fertigen Zip gebaute Binary wurde außerdem mit echten signierten HTTP-Requests für alle drei Verfahren geprüft, einschließlich eines Replays derselben Zustellung.
Für wen es ist
Für Entwickler, die Webhooks von Polar, Stripe, GitHub oder ähnlichen Diensten empfangen müssen und die unscheinbaren Teile richtig erledigt haben wollen: Signaturen, Retries, Deduplizierung und eine Queue, die einen Absturz übersteht.
Es ist für einen einzelnen Server gebaut: ein Prozess, ein SQLite-Writer, Events werden standardmäßig der Reihe nach verarbeitet. Die Zustellung ist at-least-once, Ihr Handler sollte daher idempotent sein.
Häufige Fragen
Prüft es Webhooks von Polar?
Ja. Polar verwendet Standard Webhooks, und das wird unterstützt. Als Beispiel liegt ein Handler für Polar-Bestellungen bei.
Kann ich nur den Signaturcode verwenden?
Ja. Er ist separat als kleines Modul unter MIT-Lizenz erhältlich, webhooksig. Siehe die Seite Kostenlose Tools.
Brauche ich CGO oder einen C-Compiler?
Nein. Der SQLite-Treiber ist pures Go.
Welche Go-Version?
Go 1.24 oder neuer.
Wie ist die Lizenz?
Sie dürfen es in Ihren eigenen Projekten einsetzen. Den Quellcode als konkurrierenden Starter weiterzuverkaufen ist nicht erlaubt.
Änderungsprotokoll
- 0.2.1: SQLite-Treiber auf v1.44.0 aktualisiert (Go 1.24 oder neuer erforderlich).
- 0.2.0: Standard Webhooks, Secret-Rotation, mehrere Worker, Replay- und Purge-Befehle,
/metrics, Access-Log, Polar-Beispiel, Messwerte. - 0.1.0: erstes Release.
Weiterlesen
- Webhook signature checks that fail or quietly pass in Go (Englisch)
- A durable webhook retry queue in SQLite (Englisch)
- Webhook idempotency and dedupe for SQLite and Postgres (Englisch)
- Verify Standard Webhooks (Polar) signatures in Go (Englisch)
- Receiving signed webhooks in Go with a durable queue (Englisch)
Herunterladen
Zahlung, Steuern und Rechnung übernimmt Polar als Merchant of Record. Den Download-Link erhalten Sie nach dem Checkout.