Dokumentation
1. Ein Event veröffentlichen
curl -X POST https://hookpulse.altixcode.com/api/v1/apps/APP_ID/events \
-H "Authorization: Bearer hp_…" \
-H "Content-Type: application/json" \
-d '{
"type": "invoice.paid",
"payload": { "id": "in_123", "amount": 4900 },
"dedupeKey": "invoice-123-paid"
}'Der Aufruf kehrt zurück, sobald das Event gespeichert und in die Warteschlange eingereiht ist. Übergeben Sie einen dedupeKey, dann sendet ein Retry aus Ihrem Job-Runner das Event nicht zweimal.
2. Was Ihr Kunde erhält
POST /their/endpoint
Content-Type: application/json
HookPulse-Signature: t=1700000000,v1=<base64 hmac>
HookPulse-Delivery-Id: dlv_…
HookPulse-Timestamp: 1700000000
{"id":"evt_…","type":"invoice.paid","createdAt":"…","data":{…}}3. Die Verifizierung
Geben Sie Ihren Kunden das hier. Die zwei Fehler, die alle machen: der Vergleich mit === und das Ignorieren des Zeitstempels — und beide fallen nicht auf.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verify(headers, rawBody, secret, toleranceSeconds = 300) {
const header = headers['hookpulse-signature']
const deliveryId = headers['hookpulse-delivery-id']
const timestamp = Number(/t=(\d+)/.exec(header)?.[1])
// Replay protection. Without this a captured request is valid forever.
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false
const expected = createHmac('sha256', secret)
.update(`${deliveryId}.${timestamp}.${rawBody}`)
.digest('base64')
return [...header.matchAll(/v1=([^,]+)/g)].some(([, presented]) => {
const a = Buffer.from(presented)
const b = Buffer.from(expected)
return a.length === b.length && timingSafeEqual(a, b)
})
}Verifizieren Sie gegen den rohen Body, vor jedem JSON-Parsing. Erneutes Serialisieren verändert die Bytes, und dann schlägt jede Signatur fehl.
4. Ein Secret rotieren
Beim Rotieren wird ein neues Secret gesetzt, während das alte für einen von Ihnen gewählten Zeitraum gültig bleibt. Wir signieren sofort mit dem neuen, damit Ihre Kunden zur Migration angestoßen werden — ein Empfänger, der noch gegen das alte Secret prüft, funktioniert aber weiter, bis der Zeitraum endet.
5. Das Zustellungsprotokoll
Erzeugen Sie in Ihrem Backend ein Portal-Token — aufgebaut wie eine Webhook-Signatur und auf einen Ihrer Kunden ausgestellt — und rufen Sie die Portal-API von Ihrer eigenen Einstellungsseite aus auf. Jede Abfrage ist auf den Subscriber in diesem Token beschränkt, sodass keiner Ihrer Kunden jemals die Payloads eines anderen sehen kann.
GET https://hookpulse.altixcode.com/api/portal/APP_ID/deliveries
POST https://hookpulse.altixcode.com/api/portal/APP_ID/deliveries/DELIVERY_ID/retry
Authorization: Bearer <portal token>Oder einbetten
Fügen Sie bei Pro und Scale einfach <hookpulse-log> in Ihre Einstellungsseite ein, anstatt eine eigene Tabelle zu erstellen. Gleicher Token, gleicher Gültigkeitsbereich – das Log wird direkt für Sie gerendert, und dank Shadow Root gibt es keine Konflikte mit dem CSS Ihrer Seite.
<script src="https://hookpulse.altixcode.com/embed/hookpulse-log.js"></script>
<hookpulse-log app-id="APP_ID" token="PORTAL_TOKEN" api-url="https://hookpulse.altixcode.com"></hookpulse-log>Ehrliche Grenzen
- Die Warteschlange ist eine PostgreSQL-Tabelle. Dadurch können Zustellungsprotokoll und Warteschlange nicht auseinanderlaufen, und ein Neustart geht ohne Verluste ab. Das ist nicht das schnellstmögliche Design — bei sehr hohem Durchsatz hätte ein Broker die Nase vorn.
- Der Worker läuft im Web-Prozess. Das Claiming ist bereits für mehrere Worker sicher, ihn auszulagern ist also eine Frage des Deployments und keine Neuentwicklung — aber heute teilen sich Zustell- und Request-Last einen Prozess.
- Wir folgen keinen Redirects. Täten wir es, würde eine signierte Payload an einen Host gehen, den Sie nie angegeben haben. Ein 3xx gilt als Fehlschlag.
- Ein Endpunkt, der 20 Mal in Folge fehlschlägt, wird deaktiviert. Eine einzige tote URL sollte nicht auf Kosten aller anderen weiter Versuche verbrauchen.