Zum Hauptinhalt springen

Webhooks

Mit Webhooks benachrichtigt Tourfold Ihre Systeme nahezu in Echtzeit, sobald etwas passiert — eine Marke wird angelegt, eine Tour aktualisiert, ein Einsatz gelöscht. Anstatt die API abzufragen, registrieren Sie einen HTTPS-Endpoint, an den Tourfold bei jedem Ereignis eine signierte JSON-Payload per POST sendet.

Tourfold-Webhooks folgen der offenen Spezifikation Standard Webhooks. Sie können sie daher in den meisten Sprachen mit den offiziellen standardwebhooks-Bibliotheken verifizieren und verarbeiten — ein Tourfold-spezifisches SDK ist nicht nötig.

Auf einen Blick​

TransportHTTPS POST, Content-Type: application/json
ZustellungAt-least-once — dasselbe Ereignis kann mehrfach eintreffen
ReihenfolgeNicht garantiert — ordnen Sie Ereignisse selbst über timestamp
SignierungStandard Webhooks v1 HMAC-SHA256 (Header siehe unten)
Idempotenz-SchlüsselDer Header webhook-id (über Wiederholungen hinweg stabil)
ErfolgJede HTTP-2xx-Antwort bestätigt den Empfang
AntwortfristInnerhalb von 5 Sekunden mit 2xx antworten; längere Arbeit asynchron einreihen
ZielEine öffentliche HTTPS-URL; Weiterleitungen werden nicht verfolgt
WiederholungenStandardmäßig aktiv — 1 Erstversuch plus bis zu 6 Wiederholungen (insgesamt 7 Versuche; siehe Wiederholungen)

1. Endpoint registrieren​

Sie benötigen ein API-Bearer-Token und einen über öffentliches HTTPS erreichbaren Empfänger. Fragen Sie zuerst den aktuellen Ereigniskatalog Ihres Arbeitsbereichs ab, statt eine Liste aus diesem Leitfaden fest zu hinterlegen:

curl -fsS "https://api.tourfold.com/api/v2/webhooks/event-types" \
-H "Authorization: Bearer YOUR_TOKEN" \
| jq -r '.items[].type'

Legen Sie anschließend mit createWebhookEndpoint einen Endpoint an und wählen Sie exakte Ereignisse oder Abonnement-Muster:

curl -X POST "https://api.tourfold.com/api/v2/webhook-endpoints" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Alpine maintenance integration",
"endpoint_url": "https://maintenance.example.invalid/tourfold/events",
"signature_scheme": "HMAC_SHA256",
"enabled": true,
"retry": true,
"subscriptions": ["folder.*", "*.created"]
}'

Bei erfolgreicher Erstellung erhalten Sie HTTP 201, einen Location-Header für die neue Ressource und den Endpoint:

{
"id": "00000000-0000-4000-8000-000000008001",
"display_name": "Alpine maintenance integration",
"endpoint_url": "https://maintenance.example.invalid/tourfold/events",
"signature_scheme": "HMAC_SHA256",
"enabled": true,
"retry": true,
"subscriptions": ["folder.*", "*.created"],
"invalid_subscriptions": [],
"created_at": "2026-08-20T08:40:00Z",
"updated_at": "2026-08-20T08:40:00Z",
"lock_version": 0,
"secret": "whsec_example_not_a_real_secret"
}

Das Signatur-Secret (Format whsec_…) wird nur einmal zurückgegeben: bei der Erstellung und beim Rotieren. Speichern Sie es sofort in Ihrem Secret Manager. Beim späteren Auflisten oder Abrufen des Endpoints wird es nicht erneut angezeigt.

Empfänger testen​

Nachdem das Secret im Empfänger hinterlegt ist, lösen Sie einen signierten Verbindungstest aus:

curl -X POST "https://api.tourfold.com/api/v2/webhooks/send-test" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"endpoint_id":"00000000-0000-4000-8000-000000008001"}'
{
"last_test_tried_at": "2026-08-20T09:50:00Z",
"test_was_successful": true
}

Der Aufruf sendet test.webhook sofort und synchron. Er umgeht die Abonnements, funktioniert auch bei deaktiviertem Endpoint und wird nie wiederholt. Ein fehlgeschlagener Test wird dennoch unter den Zustellfehlern erfasst. Ein Test aktualisiert den Endpoint-Zustand und erhöht dessen lock_version. Rufen Sie den Endpoint daher vor einer versionsgeschützten Änderung erneut ab.

Anforderungen an das Ziel​

In Produktion muss endpoint_url:

  • eine absolute https://-URL ohne eingebettete Zugangsdaten oder Fragment sein;
  • ausschließlich auf öffentliche IP-Adressen auflösen — Loopback-, private, Link-Local- und reservierte Ziele werden abgelehnt; und
  • zum Zustellzeitpunkt weiterhin öffentlich auflösbar sein. Tourfold löst den Namen unmittelbar vor jedem Versuch erneut auf, um DNS-Rebinding zu verhindern.

Tourfold verfolgt keine Weiterleitungen. Für den Verbindungsaufbau stehen etwa 3 Sekunden und für die Antwort 5 Sekunden zur Verfügung. Lokale HTTP-Endpoints ohne TLS funktionieren nur, wenn eine Tourfold-Entwicklungsumgebung die unsichere lokale Zustellung ausdrücklich aktiviert.

Lebenszyklus des Endpoints verwalten​

Ändern Sie einen Endpoint mit JSON Merge Patch. Lesen Sie ihn zuvor ab und senden Sie seine lock_version mit, damit Sie keine gleichzeitige Änderung überschreiben:

curl -X PATCH \
"https://api.tourfold.com/api/v2/webhook-endpoints/00000000-0000-4000-8000-000000008001" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/merge-patch+json" \
-d '{
"subscriptions": ["folder.*", "*.created"],
"lock_version": 0
}'
  • Nicht angegebene Felder bleiben unverändert. Ein angegebenes subscriptions-Array ersetzt die gesamte Menge; [] leert sie. Ein ausdrückliches null ist ungültig.
  • Jeder Endpoint akzeptiert bis zu 100 Abonnement-Muster.
  • Eine veraltete lock_version liefert HTTP 409. Rufen Sie den Endpoint erneut ab, führen Sie Ihre beabsichtigte Änderung zusammen und wiederholen Sie den Aufruf mit dem aktuellen Wert.
  • Pausieren und aktivieren Sie Zustellungen, indem Sie enabled auf false oder true setzen. Ein Test ist auch während der Pause möglich.
  • Das Löschen eines Endpoints liefert HTTP 204 und entfernt seine Konfiguration dauerhaft.

Verwalten Sie Endpoints über die Operationen unter Webhook Endpoints (auflisten, Abonnements ändern, Secret rotieren, löschen), und prüfen Sie die Erreichbarkeit jederzeit mit sendTestWebhook.

2. Ereignisnamen​

Ein Ereignisname besteht aus einem Ressourcenpfad, gefolgt von einem Verb:

brand.created
folder.permissions.updated
custom_object.invoice.created
area.created

Zwei Regeln erklären jeden Namen, der Ihnen begegnen wird:

  • Ein Punkt bedeutet Zugehörigkeit. folder.permissions sind die Berechtigungen eines Ordners — etwas anderes als der Ordner selbst, mit eigenen Ereignissen. folder.updated (der Ordner wurde bearbeitet) und folder.permissions.updated (seine Zugriffsliste hat sich geändert) sind daher unterschiedliche Ereignisse für unterschiedliche Empfänger.
  • Ein Unterstrich verbindet Wörter innerhalb eines Namensabschnitts. stored_address ist ein einzelnes Substantiv, kein stored, das eine address enthält.

Das Verb ist immer der letzte Abschnitt. created, updated und deleted sind die Grundmenge; Ressourcen können zusätzliche Zustandsübergänge anbieten, wenn diese tatsächlich in ihrem gespeicherten Statusmodell vorkommen. updated bedeutet „eine Eigenschaft wurde bearbeitet“ — es gibt nie an, welche Eigenschaft.

Ressourcen eines Plugins verwenden <plugin_key>.<resource>.<verb>. Das Präfix ist Teil von resource_path und kein separates Feld; dadurch bleibt die Zugehörigkeit in Abonnements, zugestellten Payloads und Logs sichtbar. Plugin-Ereignistypen sind laufzeitabhängige Fähigkeiten eines Mandanten: Der Ereignistyp-Katalog liefert sie nur, wenn das jeweilige Plugin für Ihren Mandanten aktiviert ist. Im statischen OpenAPI-Vertrag werden sie bewusst nicht aufgezählt. Die verbindliche Liste der aktivierbaren Typen erhalten Sie über GET /api/v2/webhooks/event-types.

Zwei Konventionen, die Sie kennen sollten:

  • deleted bedeutet, dass der Datensatz endgültig weg ist. Das Verschieben in den Papierkorb und das Wiederherstellen sind Bearbeitungen und kommen deshalb als updated an.
  • Benutzerdefinierte Objekte werden über ihren Slug benannt — custom_object.invoice.created. Siehe Benutzerdefinierte Objekte dazu, was beim Umbenennen eines Slugs passiert.

Die aktuelle Liste für Ihren Workspace liefert listWebhookEventTypes — je Ereignis den type sowie resource_path, verb und bei benutzerdefinierten Objekten definition_slug. Der Webhooks-Abschnitt der OpenAPI-Referenz zeigt die mandantenunabhängige generische Menge mit vollständigen Payload-Schemata. Nur zur Laufzeit verfügbare Plugin-Ereignistypen werden durch die Katalogantwort und das bereitstellende Plugin dokumentiert.

3. Mit Mustern abonnieren​

Ein Abonnement ist entweder ein exakter Ereignisname oder ein Muster. Über Muster abonnieren Sie breit, ohne jeden Namen einzeln aufzuführen:

MusterTrifft zu auf
brand.createdgenau dieses Ereignis
folder.*jedes Ordner-Verb und jeden Ordner-Aspekt, einschließlich folder.permissions.updated
<plugin_key>.*jede Ressource und jedes Verb eines aktivierten Plugins
*.createdcreated bei jeder Ressource
*alles
custom_object.invoice.*jedes Ereignis des benutzerdefinierten Objekts invoice
custom_object.*.createdcreated bei jedem benutzerdefinierten Objekt

Die eine Asymmetrie, die Sie sich merken sollten: ein Wildcard-Verb weitet den Pfad, ein benanntes Verb legt ihn fest. folder.* umfasst folder.permissions.updated, weil Sie den Ordner samt allem darunter abonniert haben. folder.updated umfasst es nicht, denn dieser Name bezeichnet ein einzelnes Ereignis, und Berechtigungen sind eine andere Ressource. Wenn Sie Berechtigungsänderungen möchten, abonnieren Sie sie ausdrücklich.

Präfixe greifen immer auf ganze Abschnitte, folder.* trifft also nie eine Ressource namens folder_archive. Ebenso trifft <plugin_key>.* nie auf ein generisches Ereignis wie vehicle.created. *.created und * sind dagegen bewusst global und umfassen generische Ereignisse sowie Ereignisse aktivierter Plugins; der zugestellte type enthält weiterhin das konkrete Plugin-Präfix.

Überlappende Muster sind unproblematisch. Abonniert ein Endpoint sowohl folder.* als auch folder.updated, erzeugt eine Ordnerbearbeitung dennoch genau eine Zustellung an ihn — die Entdopplung erfolgt pro Endpoint, nicht pro zutreffendem Muster.

Ein Muster, das auf nichts zutreffen kann, wird beim Speichern abgelehnt (HTTP 422), statt angenommen zu werden und dann nie etwas zuzustellen. Ein Abonnement, das ins Leere läuft, wäre der verwirrendste Fehler, den diese API erzeugen könnte — deshalb wird er vorab verhindert.

4. Payload und Header​

Jede Zustellung hat denselben Envelope. Der ereignisspezifische Teil liegt unter data.object:

{
"type": "brand.created",
"id": "1178a3d4-76c1-402d-bc51-0a411424eab2",
"event_id": "f820f8c7-3566-4c4d-a1a0-8ec2b288feab",
"timestamp": "2024-01-15T10:30:00Z",
"payload_version": 1,
"tenant_id": "34f5c98e-f430-457b-a812-92637d0c6fd0",
"actor": { "type": "USER", "id": "6b1e...c4a2" },
"request": { "id": "d7e56ac2-af7a-4e00-af77-9ffc5e02f3cb", "correlation_id": "02bde914-c402-4a49-95cd-e8a4944b85d3" },
"data": {
"object": {
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"name": "Alpine Facility Services GmbH",
"email": "contact@alpine-facility-services.example.invalid",
"phone_number": "+43123456789"
}
}
}
FeldBedeutung
typeDer Ereignisname.
idDie Id dieser Zustellung — derselbe Wert wie im Header webhook-id.
event_idDie Id des Ereignisses. Ein Ereignis an drei Endpoints ergibt drei ids und eine event_id.
timestampWann das Ereignis eingetreten ist (RFC 3339) — nicht, wann dieser Versuch signiert wurde.
payload_versionVersion des Envelope. Siehe Versionierung.
actorWer es ausgelöst hat: {"type":"USER","id":…} oder {"type":"SYSTEM"}. Fehlt, wenn nicht erfasst.
requestKorrelations-Ids für Support und Tracing. Fehlt, wenn nicht zutreffend.
data.objectDie Ressource, um die es in diesem Ereignis geht.
data.previous_attributesBei einer Bearbeitung die alten Werte der geänderten Schlüssel. Fehlt sonst.

previous_attributes​

Nur bei Bearbeitungen vorhanden, und nur mit dem, was sich geändert hat:

"data": {
"object": { "id": "…", "name": "2024 reports", "parent_id": null },
"previous_attributes": { "name": "2024", "parent_id": "9f8c…" }
}

Lesen Sie es als „was diese Schlüssel vorher waren“. Ein Schlüssel mit dem Wert null bedeutet, dass er tatsächlich leer war — ein in die oberste Ebene verschobener Ordner meldet "parent_id": null. Das ist etwas anderes als ein fehlender Schlüssel (dieser Schlüssel hat sich nicht geändert).

Was data.object garantiert​

Genau drei Dinge:

  1. Die Identität der Ressource ist immer enthalten. id bei einer Ressource, die eine hat, oder ein Verweis auf ihr übergeordnetes Objekt, wenn sie keine eigene hat — comment.reaction hat keine eigene Id und führt deshalb comment_id.
  2. Innerhalb einer payload_version werden Schlüssel nur ergänzt — nie entfernt, umbenannt oder im Typ geändert.
  3. Die Schlüsselmenge ist offen. Behandeln Sie unbekannte Schlüssel als normal und ignorieren Sie diejenigen, die Sie nicht verwenden.

Ausdrücklich nicht garantiert ist, dass data.object dem entspricht, was ein GET für dieselbe Ressource zurückgibt. Es sieht oft ähnlich aus, und sich darauf zu verlassen, geht früher oder später schief: Zu einem deleted-Ereignis gibt es kein GET, dem es entsprechen könnte, und es können Plugin-spezifische Felder auftreten, die kein GET ausliefert. Lesen Sie die Felder, die Sie brauchen, und ignorieren Sie den Rest.

HeaderBeschreibung
webhook-idEindeutige Nachrichten-Id (UUID). Über Wiederholungen hinweg stabil — nutzen Sie sie als Idempotenz-Schlüssel.
webhook-timestampUnix-Zeitstempel (Sekunden) des Signaturzeitpunkts.
webhook-signatureDie mit v1, präfigierte Signatur — siehe unten.
request-idKorrelations-Id für Support und Tracing.

5. Signatur verifizieren​

Verifizieren Sie immer, bevor Sie eine Payload parsen oder ihr vertrauen. Dieser vollständige FastAPI-Empfänger verwendet die offizielle Standard-Webhooks-Bibliothek und verifiziert die exakten Request-Bytes:

receiver.py
import json
import os

from fastapi import FastAPI, HTTPException, Request, Response
from standardwebhooks import Webhook, WebhookVerificationError

app = FastAPI()
verifier = Webhook(os.environ["TOURFOLD_WEBHOOK_SECRET"])


@app.post("/tourfold/webhooks")
async def receive_tourfold_webhook(request: Request):
raw_body = await request.body()
headers = {
"webhook-id": request.headers.get("webhook-id", ""),
"webhook-timestamp": request.headers.get("webhook-timestamp", ""),
"webhook-signature": request.headers.get("webhook-signature", ""),
}

try:
verifier.verify(raw_body, headers)
except WebhookVerificationError as exc:
raise HTTPException(status_code=400, detail="Invalid webhook signature") from exc

event = json.loads(raw_body)
# In Produktion webhook-id atomar entdoppeln und die Verarbeitung
# dauerhaft einreihen, bevor die Zustellung bestätigt wird.
print(event["type"])
return Response(status_code=204)
python -m pip install fastapi standardwebhooks uvicorn
TOURFOLD_WEBHOOK_SECRET='whsec_...' \
uvicorn receiver:app --host 0.0.0.0 --port 8000

print dient nur der Veranschaulichung. Speichern Sie in Produktion die webhook-id und reihen Sie die dauerhafte Verarbeitung ein, bevor Sie mit 2xx antworten. Langsamere Arbeit erledigen Sie danach asynchron. Protokollieren Sie weder Signatur-Secrets noch vollständige Payloads, die Kundendaten enthalten können.

Bei manueller Verifizierung: Die Signatur ist v1, gefolgt vom base64-kodierten HMAC-SHA256 der Zeichenkette {webhook-id}.{webhook-timestamp}.{raw_body}. Der Schlüssel ist Ihr Secret ohne das Präfix whsec_, dessen Rest base64-dekodiert wird; raw_body sind die exakt empfangenen Bytes (nicht erneut serialisieren):

key = base64_decode(Secret ohne das Präfix "whsec_")
signed_content = webhook_id + "." + webhook_timestamp + "." + raw_body
expected = "v1," + base64(hmac_sha256(key, signed_content))

Setzen Sie außerdem Replay-Schutz um: Lehnen Sie Zustellungen ab, deren webhook-timestamp außerhalb eines Toleranzfensters liegt (5 Minuten sind üblich).

6. Wiederholungen & Fehler​

Die Zustellung erfolgt at-least-once. Sie gilt als erfolgreich, wenn Ihr Endpoint ein beliebiges HTTP 2xx zurückgibt; alles andere (auch Timeout oder Verbindungsfehler) ist ein Fehlschlag.

Endpoints wiederholen standardmäßig (retry: true am Endpoint). Nach dem Erstversuch wird eine fehlgeschlagene Zustellung bis zu 6-mal mit steigendem Backoff wiederholt, also höchstens 7 Versuche insgesamt:

WiederholungWartezeit nach dem vorherigen Versuch
130 Sekunden
22 Minuten
310 Minuten
430 Minuten
51 Stunde
62 Stunden

Da jede Wiederholung dieselbe webhook-id trägt, entdoppeln Sie darüber: verarbeitete Ids festhalten und Wiederholungen ignorieren. Zusammen mit der Zeitstempel-Toleranz schützt Sie das vor doppelten Zustellungen und Replays.

Die Reihenfolge ist nicht garantiert. Durch Wiederholungen und parallele Zustellung kann ein späteres Ereignis ein früheres überholen — verlassen Sie sich nie auf die Eingangsreihenfolge, sondern gleichen Sie über timestamp und Ihren eigenen Zustand ab. Ein 429 wird berücksichtigt: Tourfold wartet dann mindestens 5 Minuten, oder länger, wenn Ihr Retry-After größer ist. Ein mit retry: false erstellter Endpoint wird nicht wiederholt — er schlägt beim ersten Fehlversuch endgültig fehl.

Nach dem letzten Versuch ist eine Zustellung endgültig fehlgeschlagen, und es gibt keine automatische erneute Zustellung — die Wiederherstellung liegt bei Ihnen. Sehen Sie sich aufgezeichnete Fehlschläge mit listDeliveryFailures an (eine Zeile je Nachricht, die mindestens einmal fehlgeschlagen ist; bei einem weiteren fehlgeschlagenen Versuch wird dieselbe Zeile aktualisiert). Das ist kein Zustellungsprotokoll: Erfolgreiche Versuche werden nicht aufgezeichnet, und eine später erfolgreiche Wiederholung ergänzt keinen Erfolgseintrag. Das Fehlen eines Eintrags beweist daher keinen Erfolg; ein Eintrag allein beweist keinen endgültigen Fehlschlag. Mit retry_state: EXHAUSTED erkennen Sie Nachrichten, deren Versuchsbudget ausgeschöpft wurde, und können sie bei Bedarf aus Ihrem eigenen Zustand nachholen. Fehlereinträge werden etwa 30 Tage aufbewahrt und danach gelöscht.

7. Secret rotieren​

Rotieren Sie ein kompromittiertes oder in die Jahre gekommenes Secret mit rotateWebhookEndpointSecret. Das neue whsec_…-Secret wird einmalig zurückgegeben. Die Rotation gilt sofort: Sobald die Operation erfolgreich war, ist das alte Secret ungültig; nachfolgende Zustellungen tragen genau eine Signatur mit dem neuen Secret. Koordinieren Sie die Aktualisierung des Empfängers als Cutover. Zustellungen, die eintreffen, bevor der Empfänger das neue Secret kennt, schlagen fehl und folgen der Retry-Konfiguration des Endpoints.

8. Versionierung und Kompatibilität​

Der Envelope ist über die Ganzzahl payload_version versioniert. Ereignisnamen tragen kein Versionssuffix.

Innerhalb einer payload_version nehmen wir ausschließlich rückwärtskompatible Änderungen vor:

  • Neue Felder können jederzeit zu data.object oder zum Envelope hinzukommen — ignorieren Sie unbekannte Felder, damit Ihre Integration weiterläuft, wenn sie auftreten.
  • Eine brechende Änderung erscheint als neue payload_version; die bestehende Version behält ihren Vertrag.

Konfigurieren Sie Ihren Parser so, dass er unbekannte Eigenschaften toleriert.

:::note Änderung gegenüber dem früheren Schema Ereignisnamen trugen früher ein Suffix .vN (brand.created.v1), und Breite entstand über separate „Umbrella“-Ereignisse (resource.created.v1). Beides ist entfallen: Die Versionierung liegt jetzt in payload_version des Envelope, und Breite entsteht über Abonnement-Muster.

Erzwungen hat die Änderung das Versionssuffix je Name: Es lässt sich nicht mit Präfix-Mustern vereinbaren, weil jede Versionserhöhung ein bereits gespeichertes Muster stillschweigend nicht mehr treffen würde. :::

9. Benutzerdefinierte Objekte​

Ereignisse benutzerdefinierter Objekte werden über den Slug der Definition benannt: custom_object.invoice.created.

Abonnements werden dagegen über die Id der Definition gespeichert. Dieser Unterschied ist gewollt und hat zwei Folgen:

  • Das Umbenennen einer Definition macht Ihr Abonnement nicht ungültig. Es greift weiter. Allerdings ändert sich der zugestellte type, weil der Name den aktuellen Slug wiedergibt — verankern Sie einen Slug also nicht fest in einer Routing-Logik, die Sie nicht anpassen können.

    Routen Sie stattdessen über data.object.definition_id. Jedes Ereignis eines benutzerdefinierten Objekts führt dieses Feld, und es ändert sich über die gesamte Lebensdauer der Definition nicht. Betrachten Sie type als den lesbaren Namen, der dem aktuellen Slug folgt, und definition_id als den stabilen Schlüssel:

    "data": { "object": { "id": "…", "definition_id": "6b1e0f22-…", "definition_slug": "invoice" } }
  • Löschen und Neuanlegen einer Definition mit demselben Slug belebt kein altes Abonnement. Die neu angelegte Definition ist ein anderes Objekt mit einer neuen Id.

Sie können ein Abonnement mit dem Slug oder mit der Id der Definition schreiben; beides führt zum selben gespeicherten Abonnement. Wird eine Definition gelöscht, werden ihre Abonnements in Id-Form zurückgemeldet und am Endpoint unter invalid_subscriptions aufgeführt, sodass Sie sie sehen und entfernen können.

10. Checkliste für den Produktivbetrieb​

Bevor Sie einen Endpoint für Produktivverkehr aktivieren:

  • Verifizieren Sie die Signatur über die unveränderten Request-Bytes, bevor Sie JSON parsen oder irgendeinem Feld vertrauen.
  • Erzwingen Sie ein Zeitstempel-Toleranzfenster und halten Sie die Uhren der Empfänger synchron.
  • Legen Sie einen Unique Constraint auf webhook-id; bestätigen Sie ein Duplikat, ohne es erneut anzuwenden.
  • Speichern Sie akzeptierte Zustellungen oder reihen Sie sie dauerhaft ein, bevor Sie 2xx zurückgeben, und antworten Sie innerhalb von 5 Sekunden.
  • Behandeln Sie die Payload als offenes Schema und ignorieren Sie unbekannte Felder.
  • Rechnen Sie mit mehrfach und in anderer Reihenfolge eintreffenden Ereignissen. Nutzen Sie timestamp und gleichen Sie bei relevanter Reihenfolge mit dem aktuellen Zustand der REST API ab.
  • Überwachen Sie Zustellfehler, insbesondere retry_state: EXHAUSTED, und definieren Sie einen Nachholprozess. Der Fehler-Endpoint ist kein vollständiges Zustellungsprotokoll.
  • Bewahren Sie das Signatur-Secret in einem Secret Manager auf und proben Sie die sofort wirksame Rotation.
  • Pausieren Sie den Endpoint bei Wartungsarbeiten mit enabled: false, wenn er Verkehr nicht sicher annehmen kann.

11. Fehlerbehebung​

SymptomPrüfen Sie Folgendes
Erstellung oder Änderung des Endpoints liefert 422Verwenden Sie eine absolute öffentliche HTTPS-URL ohne Zugangsdaten oder Fragment. Stellen Sie sicher, dass jede DNS-Antwort öffentlich ist und der Host aufgelöst wird.
Änderung der Abonnements liefert 422Fragen Sie den aktuellen Ereigniskatalog ab, prüfen Sie die Schreibweise der Muster und verwenden Sie höchstens 100 Muster in der Ersatzmenge.
Signaturprüfung schlägt fehlVerwenden Sie das einmalig ausgegebene Secret dieses Endpoints, verifizieren Sie die unveränderten Bytes und alle drei webhook-*-Header, prüfen Sie die Uhrabweichung und beachten Sie, dass eine Rotation das alte Secret sofort ungültig macht.
Ein Test liefert test_was_successful: falsePrüfen Sie öffentliche Erreichbarkeit, ein vertrauenswürdiges TLS-Zertifikat und eine 2xx-Antwort innerhalb von 5 Sekunden. Weiterleitungen werden nicht verfolgt. Prüfen Sie den Fehlereintrag für test.webhook.
Ein PATCH liefert nach einem Test 409Der Test hat den Endpoint-Zustand aktualisiert und lock_version erhöht. Rufen Sie den Endpoint erneut ab, führen Sie Ihre Änderung zusammen und wiederholen Sie sie mit der aktuellen Version.
Dasselbe Ereignis wird zweimal verarbeitetAt-least-once-Zustellung verhält sich wie vorgesehen. Entdoppeln Sie über webhook-id, die bei Wiederholungen stabil bleibt.
Ereignisse scheinen in falscher Reihenfolge einzutreffenZustellungen laufen parallel, und Wiederholungen können neuere Versuche überholen. Nutzen Sie nicht die Eingangsreihenfolge, sondern timestamp und den aktuellen Ressourcenzustand.
invalid_subscriptions ist nicht leerEine referenzierte Definition eines benutzerdefinierten Objekts wurde gelöscht. Senden Sie per PATCH die vollständige gewünschte subscriptions-Menge ohne die ungültigen Einträge.
Ein Fehler zeigt retry_state: EXHAUSTEDDie automatischen Versuche sind beendet. Reparieren Sie den Empfänger und gleichen Sie anschließend über die REST API ab oder holen Sie Daten nach; es gibt keinen Endpoint zur erneuten Zustellung.
Es ist kein Fehler aufgeführtDas beweist keine Zustellung. Nur fehlgeschlagene Nachrichten werden erfasst, Einträge verfallen nach etwa 30 Tagen und erfolgreiche Versuche bilden kein durchsuchbares Protokoll.

Nächste Schritte​