Webhooks und Kalender
Neben der lesenden REST-API stellen Integrationen einen Kalenderfeed und ausgehende Webhooks bereit. Beides wird unter Integrationen von Owner und Admin verwaltet. Der Stand ist Beta.
Kalenderfeed
Abschnitt betitelt „Kalenderfeed“Owner und Admins erzeugen eine einmalig sichtbare URL:
https://api.orbinaut.ccl-dev.com/api/calendar/v1/YOUR_CALENDAR_TOKEN.icsDer Token beginnt mit orb_cal_. Die URL ist mit Google Calendar, Apple
Calendar und Outlook abonnierbar. Sie enthält nur veröffentlichte, geplante
Kurstermine des Workspaces.
| Enthalten | Nicht enthalten |
|---|---|
| Kurstitel, Beschreibung, Ort | Buchungen |
Beginn und Ende über DTSTART/DTEND |
Teilnehmername und E-Mail |
text/calendar; charset=utf-8 |
interne Online-Zugangsdaten |
Alle Zeitstempel stehen in UTC und enden auf Z. Der Feed liefert keine
VTIMEZONE-Komponente und keine Zeitzone je Termin. Kalenderprogramme
rechnen selbst in die Anzeigezeitzone um. Die Zeitzone der Durchführung steht
dagegen im REST-Feld timeZone der Sitzung
(Referenz).
Der Feed enthält höchstens 1.000 Termine. Berücksichtigt werden Termine, deren Start zwischen 30 Tagen vor und ausschließlich 365 Tagen nach dem Abrufzeitpunkt liegt. Bei mehr Treffern liefert der Feed die frühesten 1.000 Termine, stabil nach Startzeit und Termin-ID sortiert.
Das Polling-Limit gilt über alle Serverinstanzen hinweg:
| Grenze | Budget |
|---|---|
| Je Feed | 60 Abrufe in 15 Minuten |
| Je Client | 240 Abrufe in 15 Minuten |
GET und HEAD verbrauchen dasselbe Budget. Bei Überschreitung antwortet der
Feed mit 429 / calendar_rate_limit_exceeded. Der Header Retry-After
nennt die verbleibenden Sekunden des betroffenen Fensters. Der im
Dashboard sichtbare Wert Zuletzt abgerufen wird höchstens einmal pro
Stunde aktualisiert.
Antworten setzen Cache-Control: private, no-store und
Content-Disposition: inline; filename="orbinaut-calendar.ics". Unbekannte,
widerrufene oder rotierte Tokens sowie Pfade ohne .ics liefern 404 /
calendar_not_found.
Enthält der Plan des Workspace keine Kalenderfeeds, antwortet der Feed mit
403 / capability_not_in_plan. Ein neuer Token ändert daran nichts; nötig
ist ein Planwechsel (Plan und Abrechnung).
Der Feed ist kein REST-JSON-Endpunkt und steht nicht in
public-v1.yaml. Google-Kalender-Synchronisation bleibt Roadmap; Version 1 liefert
nur den iCal-Export.
Webhooks
Abschnitt betitelt „Webhooks“Ausgehende Webhooks sind Beta. Der Katalog ist ein lesender Spiegel der
Workspace-bezogenen Datenänderungen. Jedes Event entsteht in derselben
Datenbanktransaktion wie die beschriebene Änderung. Für jeden aktiven
Endpunkt, der den Typ oder * abonniert, entsteht ein eigener
Outbox-Datensatz mit derselben Event-ID.
Endpunkte wählst du unter Webhooks. Alle Events (auch künftige) speichert
["*"] und empfängt alle aktuellen und zukünftigen Typen. Eine explizite
Liste bleibt möglich. booking.created bleibt für bestehende Empfänger
kompatibel: type, data.booking und data.course bleiben; neue Envelope-
und Snapshot-Felder kommen additiv hinzu.
Nicht Teil des Katalogs: Plattform-Newsletter, Orbi-Gespräche, SaaS-Abo, Widget-Analytics, Audit-Events, Integrations-Geheimnisse und Mail-Jobzustände. Eingehende Webhooks, eine native Zapier-App und Google-Kalender-Sync bleiben Roadmap.
Event-Katalog
Abschnitt betitelt „Event-Katalog“| Typ | Auslöser | Nur mit Teilnehmerdaten mitsenden |
|---|---|---|
contact.created |
Kontakt angelegt | name, email, phone, postalCode, city, countryCode |
contact.updated |
Kontakt geändert; data.changes listet geänderte Felder |
dieselben Felder |
contact.deleted |
Kontakt gelöscht | keine; Payload enthält nur id |
course.created |
Kursdurchführung angelegt | keine |
course.updated |
Kurs geändert ohne Statuswechsel | keine |
course.published |
Kurs veröffentlicht | keine |
course.ended |
Kurs beendet | keine |
course.cancelled |
Kurs abgesagt | keine |
course.deleted |
Kurs gelöscht | keine |
session.created |
Termin angelegt | keine |
session.updated |
Termin geändert | keine |
session.cancelled |
Termin abgesagt | keine |
session.reactivated |
Termin reaktiviert | keine |
session.deleted |
Termin gelöscht | keine |
session_trainer.assigned |
Trainer:in einem Termin zugewiesen | keine |
session_trainer.unassigned |
Trainer:in von einem Termin entfernt | keine |
booking.created |
Buchung angelegt, auch mit Status waitlisted oder pending |
participant, billing |
booking.confirmed |
Buchung bestätigt | participant, billing |
booking.cancelled |
Buchung storniert | participant, billing |
booking.expired |
Reservierung oder anonyme Warteliste abgelaufen | participant, billing |
booking.waitlisted |
zusätzlich zu booking.created, wenn der Status waitlisted ist |
participant, billing |
booking.waitlist_promoted |
Nachrücken von der Warteliste | participant, billing |
booking.updated |
Buchung geändert ohne Statuswechsel | participant, billing |
attendance.recorded |
Anwesenheit erfasst | keine |
participation_confirmation.issued |
Teilnahmebestätigung ausgestellt | participant |
participation_confirmation.revoked |
Teilnahmebestätigung widerrufen | participant |
package.created |
Paket angelegt | keine |
package.updated |
Paket geändert | keine |
package.published |
Paket veröffentlicht | keine |
package.ended |
Paket beendet | keine |
package.cancelled |
Paket abgesagt | keine |
package.deleted |
Paket gelöscht | keine |
package_booking.created |
Paketbuchung angelegt | participant |
package_booking.confirmed |
Paketbuchung bestätigt | participant |
package_booking.cancelled |
Paketbuchung storniert | participant |
package_booking.expired |
Paketbuchung abgelaufen | participant |
payment.created |
Zahlung angelegt | keine |
payment.succeeded |
Zahlung erfolgreich | keine |
payment.failed |
Zahlung fehlgeschlagen | keine |
payment.refunded |
Zahlung vollständig erstattet | keine |
payment.partially_refunded |
Zahlung teilweise erstattet | keine |
payment.disputed |
Zahlung beanstandet | keine |
payment.cancelled |
Zahlung abgebrochen | keine |
refund.requested |
Erstattung angefordert | keine |
refund.succeeded |
Erstattung erfolgreich | keine |
refund.failed |
Erstattung fehlgeschlagen | keine |
refund.cancelled |
Erstattung abgebrochen | keine |
invoice.issued |
Rechnung ausgestellt | buyer |
credit_note.issued |
Gutschrift ausgestellt | buyer |
venue.created |
Filiale angelegt | contactName, contactEmail, contactPhone |
venue.updated |
Filiale geändert | dieselben Felder |
venue.default_changed |
Standard-Filiale gewechselt | dieselben Felder |
venue.deleted |
Filiale gelöscht | keine |
trainer.created |
Trainer:in angelegt | email, phone |
trainer.updated |
Trainer:in geändert | email, phone |
trainer.archived |
Trainer:in archiviert | email, phone |
trainer.restored |
Trainer:in wiederhergestellt | email, phone |
trainer_absence.created |
Abwesenheit angelegt | keine; note wird nie gesendet |
trainer_absence.deleted |
Abwesenheit gelöscht | keine |
holiday.created |
Feiertag angelegt | keine |
holiday.deleted |
Feiertag gelöscht | keine |
member.joined |
Mitglied beigetreten | name, email |
member.role_changed |
Rolle geändert | name, email |
member.removed |
Mitglied entfernt | name, email |
member.left |
Mitglied hat den Arbeitsbereich verlassen | name, email |
member.ownership_transferred |
Eigentümerschaft übertragen | name, email |
invitation.created |
Einladung erstellt | email |
invitation.resent |
Einladung erneut gesendet | email |
invitation.cancelled |
Einladung widerrufen | email |
invitation.accepted |
Einladung angenommen | email |
media.created |
Medium angelegt | keine; storageKey wird nie gesendet |
media.updated |
Medium geändert | keine |
media.reordered |
Medienreihenfolge geändert | keine |
media.deleted |
Medium gelöscht | keine |
staff_message.sent |
Nachricht an alle gesendet | keine; der Nachrichtentext fehlt |
workspace.profile_updated |
Unternehmensprofil geändert | keine |
ping |
Testevent senden im Dashboard | keine; data ist {} |
Ein Statuswechsel erzeugt nur das Lebenszyklus-Event, kein zusätzliches
updated. Feldänderungen ohne Statuswechsel erzeugen updated mit
data.changes. Leere Diffs erzeugen kein Event. *.deleted trägt
data.<resource>: { "id": "…" } und data.deletedAt.
Envelope
Abschnitt betitelt „Envelope“Unveränderte äußere Form, drei additive Felder:
{ "id": "event-…", "type": "course.published", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "course", "id": "course-…" }, "data": { "course": { "id": "course-…", "title": "Aquarellkurs", "status": "published" } }}| Feld | Typ | Bedeutung |
|---|---|---|
id |
string | Event-ID, identisch mit x-orbinaut-event-id |
type |
string | Katalogtyp, zum Beispiel booking.created oder ping |
occurredAt |
string | Zeitpunkt der Änderung, ISO 8601 in UTC |
workspaceId |
string | ID des Arbeitsbereichs |
resource.type |
string | Primäre Ressource, zum Beispiel booking |
resource.id |
string | ID der Primärressource |
data.<resource> |
object | Vollständiger Snapshot nach der Änderung |
Jeder Snapshot enthält id sowie, wo vorhanden, createdAt und updatedAt.
Empfänger werten bei mehreren Schreibvorgängen den neuesten Wert von updatedAt aus. Die Zustellreihenfolge
über mehrere Events hinweg ist nicht garantiert.
Beispiele je Ressourcenfamilie
Abschnitt betitelt „Beispiele je Ressourcenfamilie“booking.created bleibt kompatibel. Additive Felder: workspaceId,
resource und weitere Snapshot-Felder. data.participant nur mit
Teilnehmerdaten mitsenden.
{ "id": "event-…", "type": "booking.created", "occurredAt": "2026-08-17T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "booking", "id": "booking-…" }, "data": { "booking": { "id": "booking-…", "source": "widget", "status": "confirmed", "courseId": "course-…", "priceInCents": 0, "currency": "EUR" }, "course": { "id": "course-…", "title": "Aquarellkurs" } }}{ "id": "event-…", "type": "contact.created", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "contact", "id": "participant-…" }, "data": { "contact": { "id": "participant-…", "createdAt": "2026-09-01T12:00:00.000Z" } }}{ "id": "event-…", "type": "course.published", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "course", "id": "course-…" }, "data": { "course": { "id": "course-…", "title": "Aquarellkurs", "status": "published" } }}{ "id": "event-…", "type": "session.updated", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "session", "id": "session-…" }, "data": { "session": { "id": "session-…", "courseId": "course-…", "startsAt": "2026-09-08T17:00:00.000Z" }, "changes": ["startsAt"] }}{ "id": "event-…", "type": "session_trainer.assigned", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "session_trainer", "id": "session-…" }, "data": { "session": { "id": "session-…" }, "trainer": { "id": "trainer-…" } }}{ "id": "event-…", "type": "attendance.recorded", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "attendance", "id": "attendance-…" }, "data": { "attendance": { "id": "attendance-…", "bookingId": "booking-…", "sessionId": "session-…", "status": "present" } }}{ "id": "event-…", "type": "participation_confirmation.issued", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "participation_confirmation", "id": "pc-…" }, "data": { "participation_confirmation": { "id": "pc-…", "bookingId": "booking-…", "downloadAvailable": true } }}{ "id": "event-…", "type": "package.published", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "package", "id": "package-…" }, "data": { "package": { "id": "package-…", "title": "10er-Karte", "status": "published" } }}{ "id": "event-…", "type": "package_booking.confirmed", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "package_booking", "id": "pkg-booking-…" }, "data": { "package_booking": { "id": "pkg-booking-…", "status": "confirmed", "packageId": "package-…" } }}{ "id": "event-…", "type": "payment.succeeded", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "payment", "id": "pay-…" }, "data": { "payment": { "id": "pay-…", "status": "paid", "amountInCents": 4900, "currency": "EUR" } }}{ "id": "event-…", "type": "refund.succeeded", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "refund", "id": "refund-…" }, "data": { "refund": { "id": "refund-…", "paymentId": "pay-…", "status": "succeeded", "amountInCents": 4900 } }}{ "id": "event-…", "type": "invoice.issued", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "invoice", "id": "inv-…" }, "data": { "invoice": { "id": "inv-…", "invoiceNumber": "RE-2026-0001", "bookingId": "booking-…", "totalGrossMinor": 4900 } }}{ "id": "event-…", "type": "credit_note.issued", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "credit_note", "id": "cn-…" }, "data": { "credit_note": { "id": "cn-…", "creditNoteNumber": "GS-2026-0001", "invoiceId": "inv-…", "refundId": "refund-…" } }}{ "id": "event-…", "type": "venue.created", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "venue", "id": "venue-…" }, "data": { "venue": { "id": "venue-…", "name": "Atelier Nord", "city": "Hamburg" } }}{ "id": "event-…", "type": "trainer.created", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "trainer", "id": "trainer-…" }, "data": { "trainer": { "id": "trainer-…", "name": "Kim Beispiel", "kind": "external" } }}{ "id": "event-…", "type": "trainer_absence.created", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "trainer_absence", "id": "absence-…" }, "data": { "trainer_absence": { "id": "absence-…", "trainerId": "trainer-…", "startsAt": "2026-09-10T08:00:00.000Z" } }}{ "id": "event-…", "type": "holiday.created", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "holiday", "id": "holiday-…" }, "data": { "holiday": { "id": "holiday-…", "observedOn": "2026-12-25", "name": "1. Weihnachtstag" } }}{ "id": "event-…", "type": "member.joined", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "member", "id": "user-…" }, "data": { "member": { "userId": "user-…", "role": "trainer" } }}{ "id": "event-…", "type": "invitation.created", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "invitation", "id": "invitation-…" }, "data": { "invitation": { "id": "invitation-…", "role": "trainer", "status": "pending" } }}{ "id": "event-…", "type": "media.created", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "media", "id": "media-…" }, "data": { "media": { "id": "media-…", "courseId": "course-…", "kind": "image", "visibility": "public" } }}media.reordered sendet statt eines Snapshots data.mediaIds in der neuen Reihenfolge.
{ "id": "event-…", "type": "staff_message.sent", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "staff_message", "id": "msg-…" }, "data": { "staff_message": { "id": "msg-…", "courseId": "course-…", "subject": "Treffpunkt", "recipientCount": 8 } }}{ "id": "event-…", "type": "workspace.profile_updated", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "workspace", "id": "org-…" }, "data": { "workspace": { "id": "org-…", "slug": "atelier-nord", "name": "Atelier Nord" } }}{ "id": "event-…", "type": "ping", "occurredAt": "2026-09-01T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "ping", "id": "webhook-endpoint-…" }, "data": {}}Reihenfolge, Idempotenz und Aufbewahrung
Abschnitt betitelt „Reihenfolge, Idempotenz und Aufbewahrung“Ein Worker prüft alle 5 Sekunden auf fällige Zustellungen. Das Event
erreicht den Empfänger in der Regel wenige Sekunden nach der Änderung, nie
synchron. Die Zustellung ist mindestens einmal: Timeout oder Abbruch
wiederholt dasselbe Event mit derselben x-orbinaut-event-id.
Die Reihenfolge über mehrere Events hinweg ist nicht garantiert. Empfänger
deduplizieren über id bzw. x-orbinaut-event-id und ordnen über
occurredAt sowie Snapshot-updatedAt. Es gibt keinen historischen Replay
älterer Events; der Anfangsstand kommt über die lesende REST-API.
Zugestellte Outbox-Zeilen werden nach 30 Tagen gelöscht, endgültig fehlgeschlagene nach 90 Tagen. Offene Zustellungen bleiben.
Zustellung
Abschnitt betitelt „Zustellung“| Eigenschaft | Wert |
|---|---|
| Methode | POST |
| Content-Type | application/json |
| User-Agent | Orbinaut-Webhooks/1.0 |
| Timeout | 10 Sekunden je Versuch |
| Erfolg | HTTP-Status 200 bis 299 |
| Versuche | höchstens 8 |
| Weiterleitungen | Orbinaut folgt ihnen nicht; 3xx zählt als Fehler |
| Antwortkörper | wird ignoriert |
| TLS | Zertifikat muss zum Hostnamen der URL passen |
Jeder Versuch ist ein eigener POST mit neuem x-orbinaut-timestamp und
neuer Signatur. Event-ID und Body bleiben über alle Versuche gleich.
Empfänger müssen anhand der Event-ID idempotent verarbeiten.
Als Fehler gelten ein Status außerhalb von 2xx, ein Timeout, ein
Verbindungs- oder TLS-Fehler und eine URL, die zum Zeitpunkt der Zustellung
auf ein privates Netz auflöst.
Wiederholungen
Abschnitt betitelt „Wiederholungen“Nach einem fehlgeschlagenen Versuch wartet Orbinaut mit exponentiellem Backoff. Die Wartezeit verdoppelt sich ab 5 Sekunden; der Worker-Takt von 5 Sekunden kommt hinzu.
| Versuch | Frühestens nach dem vorherigen Versuch |
|---|---|
| 1 | sofort |
| 2 | 5 Sekunden |
| 3 | 10 Sekunden |
| 4 | 20 Sekunden |
| 5 | 40 Sekunden |
| 6 | 80 Sekunden |
| 7 | 160 Sekunden |
| 8 | 320 Sekunden |
Scheitert auch der achte Versuch, gilt das Event als Endgültig
fehlgeschlagen. In Zustellungen kannst du Erneut senden wählen;
Orbinaut setzt den Versuchszähler zurück. Testevent senden legt ein Event
ping nur für diesen Endpunkt an. Insgesamt liegen zwischen dem ersten und
dem letzten automatischen Versuch rund 11 Minuten.
Anforderungen an die URL
Abschnitt betitelt „Anforderungen an die URL“Die URL wird beim Anlegen und vor jeder Zustellung geprüft; ein Endpunkt lässt sich nicht bearbeiten, ein anderes Ziel ist ein neuer Webhook.
| Regel | Details |
|---|---|
| Schema | nur https:// |
| Länge | höchstens 2.048 Zeichen |
| Benutzerdaten | keine user:password@ in der URL |
| Hostname | nicht localhost und keine Endungen .localhost, .local, .internal, .home, .lan |
| IP-Adressen | keine privaten oder Loopback-Adressen, auch nicht als IPv6-gemappte IPv4-Adresse |
| DNS | der Hostname darf zu keinem Zeitpunkt auf eine private Adresse auflösen |
Orbinaut speichert die normalisierte Form der URL. Löst der Hostname später auf eine private Adresse auf, schlägt die Zustellung fehl und wird wie ein Empfängerfehler wiederholt.
| Header | Bedeutung |
|---|---|
x-orbinaut-event-id |
stabile Idempotenz-ID, für alle Versuche gleich |
x-orbinaut-timestamp |
Unix-Zeit in Sekunden zum Zeitpunkt des Versuchs |
x-orbinaut-signature |
v1=<hex-hmac>; es gibt nur die Version v1 |
Die Signatur ist HMAC-SHA256(secret, "<timestamp>.<raw-body>"), hex-kodiert
in Kleinbuchstaben. Empfänger prüfen den Timestamp gegen ein enges
Zeitfenster, etwa fünf Minuten, berechnen die Signatur über den
unveränderten Request-Body und vergleichen sie in konstanter Zeit. Das
Signing-Secret beginnt mit whsec_ und ist nur einmal sichtbar. Nach einer
Rotation signiert Orbinaut alle weiteren Zustellungen mit dem neuen Secret,
auch Wiederholungen bereits wartender Events.
Payload
Abschnitt betitelt „Payload“Der Body ist die JSON-Serialisierung ohne Leerzeichen und Zeilenumbrüche. Die
Signatur gilt für genau diese Bytes. Form und Felder stehen unter Envelope und
Event-Katalog. booking.created behält data.booking.id, data.booking.source,
data.booking.status und data.course; workspaceId und resource sind
additiv.
Payload mit Teilnehmerdaten
Abschnitt betitelt „Payload mit Teilnehmerdaten“Nur der explizite Webhook-Scope participants:read, im Dashboard das Häkchen
Teilnehmerdaten mitsenden, ergänzt personenbezogene Blöcke. Bei
booking.created bleibt data.participant mit name und email erhalten.
{ "id": "event-…", "type": "booking.created", "occurredAt": "2026-08-17T12:00:00.000Z", "workspaceId": "org-…", "resource": { "type": "booking", "id": "booking-…" }, "data": { "booking": { "id": "booking-…", "source": "admin", "status": "pending" }, "course": { "id": "course-…", "title": "Aquarellkurs" }, "participant": { "name": "Vorname Nachname", "email": "teilnehmer@example.com" } }}| Feld | Typ | Bedeutung |
|---|---|---|
data.participant.name |
string | Name zum Buchungszeitpunkt |
data.participant.email |
string | E-Mail-Adresse; leerer String, wenn die Buchung ohne E-Mail angelegt wurde |
Ohne den Scope fehlt das Objekt vollständig. Der Scope wird beim Anlegen des
Events ausgewertet: Ein späteres Setzen des Häkchens ändert wartende Events
nicht. internalNotes, Trainer-note, Rechnungs-PDFs und Secrets gehören
nie in den Payload.
Endpunkt-Zustände
Abschnitt betitelt „Endpunkt-Zustände“| Zustand | Wirkung |
|---|---|
| Aktiv | Neue Änderungen erzeugen Events, fällige Zustellungen laufen. |
| Deaktiviert | Neue Änderungen erzeugen für diesen Endpunkt kein Event. Bereits wartende Zustellungen pausieren und laufen nach Aktivieren weiter. |
| Plan ohne Webhooks | Für den Workspace entstehen keine Events. Wartende Zustellungen gelten als endgültig fehlgeschlagen. Im Dashboard steht Webhooks sind derzeit gesperrt. |
Endpunkte lassen sich nicht löschen. Deaktivieren ist der Endzustand.
Verifikation (Skizze)
Abschnitt betitelt „Verifikation (Skizze)“import { createHmac, timingSafeEqual } from "node:crypto";
function verifyOrbinautSignature(secret, timestamp, rawBody, header) { const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp)); if (!Number.isFinite(ageSeconds) || ageSeconds > 300) return false; const expected = `v1=${createHmac("sha256", secret) .update(`${timestamp}.${rawBody}`, "utf8") .digest("hex")}`; const a = Buffer.from(header); const b = Buffer.from(expected); return a.length === b.length && timingSafeEqual(a, b);}Der Empfänger antwortet mit 2xx, sobald das Event gespeichert ist, und
verarbeitet es danach. Lange Verarbeitung innerhalb der 10 Sekunden führt zu
Timeouts und doppelten Zustellungen.
secret, YOUR_API_KEY und echte Zustell-URLs gehören nicht in diese
Dokumentation. Verwende zum Testen einen eigenen HTTPS-Empfänger, der nicht
in ein privates Netz auflöst. Incoming-Webhooks von Discord oder Slack
sind ungeeignet: Sie erwarten ein Chat-Nachrichtenformat und lehnen das
Orbinaut-Envelope mit HTTP 400 ab.
Eine native Zapier-App ist Roadmap. Version 1 liefert Zapier-kompatible, signierte Webhook-Events, keine Synchronisation mit externen Anbietern.
Die REST-Endpunkte bleiben in der Referenz.

