Zum Inhalt springen

Webhook-Trigger: Workflows von außen starten

Lass dein ERP oder CRM einen Browserberg-Workflow per signierter Zustellung starten: das genaue HMAC-Rezept, der Replay-Schutz und die eine identische 401.

Zuletzt aktualisiert:

Eine signierte Zustellung

Dein Sender hält das Trigger-Secret signature = HMAC-SHA256(secret, deliveryId . timestamp . rawBody ) die rohen Bytes -- nicht das neu geparste JSON POST /v1/trigger-hooks/:id prüft, dann läuft der Run Drei Ablehnungen, eine identische Antwort falsche Signatur · unbekannter Trigger · Zeitstempel außerhalb des 5-Minuten-Fensters die Delivery-ID liegt UNTER der Signatur: eine mitgeschnittene Zustellung unter neuer ID ändert den signierten String -- der Dedup-Schlüssel dedupliziert nur IDs, die der SENDER wählte
Die Delivery-Id liegt unter der Signatur; eine mitgeschnittene Zustellung lässt sich also nicht unter neuer Id wiederholen.

Der eine öffentliche Endpunkt

POST /v1/trigger-hooks/:id ist der einzige Endpunkt der API, der Anfragen ohne API-Schlüssel akzeptiert — er muss es sein, denn der Aufrufer ist dein ERP oder CRM, nicht dein Code. Geht eine Bestellung ein, schließt ein Vorgang oder wird eine Rechnung gebucht, kann das System, das davon weiß, den Workflow direkt starten.

An die Stelle des API-Schlüssels treten ein Secret je Trigger und eine Signatur über jede Zustellung.

Signieren und zustellen

Voraussetzungen: api-key kek

BASE="https://browserberg.com"
AUTH="Authorization: Bearer $BROWSERBERG_API_KEY"
JSON="Content-Type: application/json"

WFID=$(curl -s -X POST "$BASE/v1/workflows" -H "$AUTH" -H "$JSON" -d '{
  "title": "Webhook walkthrough",
  "blocks": [ { "blockType": "navigation", "label": "Open_page", "url": "https://example.com" } ]
}' | python3 -c 'import json,sys; print(json.load(sys.stdin)["workflow"]["workflowId"])')

# The secret comes back exactly once, at creation. Store it; it cannot be read
# back, only rotated.
CREATED=$(curl -s -X POST "$BASE/v1/workflows/$WFID/triggers" -H "$AUTH" -H "$JSON" \
  -d '{ "kind": "webhook", "name": "ERP delivery", "session": { "ttlSeconds": 300 } }')
SECRET=$(echo "$CREATED" | python3 -c 'import json,sys; print(json.load(sys.stdin)["secret"])')
TID=$(echo "$CREATED" | python3 -c 'import json,sys; print(json.load(sys.stdin)["trigger"]["id"])')

# Sign a delivery: HMAC-SHA256 over deliveryId.timestamp.rawBody -- the raw
# bytes you send, never a re-serialisation.
BODY='{"inputs":{}}'
DELIVERY="delivery-$(date +%s)"
TS=$(date +%s)
SIG=$(python3 -c "import hmac, hashlib, sys
delivery, ts, body, secret = sys.argv[1:5]
print(hmac.new(secret.encode(), f'{delivery}.{ts}.{body}'.encode(), hashlib.sha256).hexdigest())" \
  "$DELIVERY" "$TS" "$BODY" "$SECRET")

curl -s -X POST "$BASE/v1/trigger-hooks/$TID" -H "$JSON" \
  -H "x-browserberg-delivery: $DELIVERY" \
  -H "x-browserberg-timestamp: $TS" \
  -H "x-browserberg-signature: v1=$SIG" \
  -d "$BODY" \
  | python3 -c 'import json,sys; f = json.load(sys.stdin)["firing"]; print(json.dumps({
      "outcome": f["outcome"]}, indent=2))'

curl -s -X DELETE "$BASE/v1/triggers/$TID" -H "$AUTH" > /dev/null

Das Rezept, präzise

Berechne HMAC-SHA256 über deliveryId.timestamp.rawBody — die rohen Bytes, die du sendest, nie eine Re-Serialisierung des JSON — und schicke das Ergebnis als Hex in Kleinbuchstaben, optional mit v1=-Präfix. Der Timestamp sind Unix-Sekunden und darf höchstens 300 Sekunden von der Serveruhr abweichen. Der Content-Type muss application/json sein; alles andere wird vor dem Parsen abgewiesen.

Die Delivery-Id ist der Dedup-Schlüssel und liegt mit Absicht unter der Signatur: Dedup allein filtert nur Ids, die der Absender selbst gewählt hat — eine mitgeschnittene Zustellung unter jeweils frischer Id würde jedes Mal einen Run starten. Doch eine andere Id ändert den signierten String, und die Signatur stirbt mit ihm. Ein unbekannter Trigger und eine falsche Signatur bekommen ein und dieselbe 401 — wer den Endpunkt absucht, lernt nichts.

Secrets und Rotation

Das whs_...-Secret erscheint genau einmal, in der Antwort auf das Anlegen; hinterlege es in der Konfiguration deines Absenders, denn abrufen lässt es sich kein zweites Mal. POST /v1/triggers/:id/secret rotiert es mit einer Karenzzeit von bis zu 7 Tagen, in der das alte Secret weiter gilt — genug Zeit, um den Absender ohne Zustelllücke neu auszurollen.

Hinweis · Key-Provider auf Self-Hosted-Setups nötig

Webhook-Trigger-Secrets werden mit dem Tenant-Schlüssel versiegelt. Eine selbst gehostete Installation braucht daher OVHcloud KMS oder, außerhalb der Produktion, einen lokalen KEK; fehlt beides, antwortet die API mit 503 capacity_unavailable und sagt, welche Konfiguration fehlt.

Mehr zu Triggern

  • Zeitplan-Trigger Das uhrgetriebene Gegenstück
  • Referenz Trigger Header, Limits und Firing-Ausgänge
  • Fehler und Wiederholungen Wie sich 401 und 202 in den Umschlag fügen