Webhooks
Mit Webhooks reagieren Deine Systeme auf Ereignisse in Deinem Usetix-Konto — ein verkauftes Ticket, eine ausgelöste Rückerstattung, ein live geschaltetes Event — ohne unsere API abfragen zu müssen. Sobald ein abonniertes Ereignis eintritt, sendet Usetix eine signierte POST-Anfrage an eine von Dir festgelegte URL.
Webhooks verwaltest Du in Deinem Dashboard unter Einstellungen → Webhooks.
Zustellung
| Eigenschaft | Wert |
|---|---|
| Methode | POST |
| Content-Type | application/json |
| User-Agent | usetix/1.0.0 Webhook |
| Timeout | 7 Sekunden |
| Maximale Antwortgröße | 100 KB |
Jedes Ereignis erzeugt genau einen Zustellversuch pro passendem Webhook. Automatische Wiederholungen gibt es derzeit nicht — stell also sicher, dass Dein Endpunkt erreichbar ist und zügig antwortet.
Signierung
Jede Anfrage wird per HMAC-SHA256 mit dem signing_secret des Webhooks signiert. Das Secret wird Dir im Dashboard angezeigt, sobald Du den Webhook anlegst. Zwei Header werden mitgeschickt:
| Header | Beschreibung |
|---|---|
X-Webhook-Signature |
Hex-kodierter HMAC-SHA256 des rohen Request-Body, erzeugt mit dem Signing-Secret des Webhooks. |
X-Webhook-Timestamp |
ISO-8601-Zeitstempel (UTC) des Ereignisses — bleibt bei etwaigen künftigen Wiederholungen stabil. |
Verifizierung in Ruby:
expected = OpenSSL::HMAC.hexdigest("SHA256", signing_secret, request.raw_post)
Rack::Utils.secure_compare(expected, request.headers["X-Webhook-Signature"])
Verifizierung in Node.js:
const expected = crypto
.createHmac("sha256", signingSecret)
.update(rawBody)
.digest("hex");
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.header("X-Webhook-Signature")));
Berechne die Signatur immer über den rohen Request-Body — nicht über eine geparste oder neu serialisierte Kopie — und nutze einen zeitkonstanten Vergleich.
Automatische Deaktivierung
Wenn ein Webhook bei 10 aufeinanderfolgenden Zustellungen über mehr als 1 Stunde fehlschlägt, deaktiviert Usetix ihn automatisch. Es werden keine weiteren Zustellungen mehr versendet, bis Du den Webhook im Dashboard wieder aktivierst. Ein Endpunkt gilt als erfolgreich, wenn er HTTP 2xx zurückgibt; alles andere (Timeout, Nicht-2xx, TLS-Fehler, DNS-Fehler) zählt als Fehlschlag.
SSRF-Schutz
Webhook-URLs, die auf private, Loopback-, Link-Local- oder andere nicht-öffentliche IP-Adressen auflösen, werden bei der Zustellung abgelehnt. Verwende ausschließlich öffentliche Hostnamen.
Abonnierbare Ereignisse
Jeder Webhook abonniert eine oder mehrere der folgenden Aktionen:
order.paidorder.refundedorder.cancelledevent.publishedevent.unpublished
Payload-Envelope
Jeder Payload hat dieselbe äußere Struktur. Das eventable-Objekt variiert je nach Aktion.
{
"action": "order.paid",
"created_at": "2026-04-22T12:34:56Z",
"eventable": { "...": "siehe unten" },
"account": {
"name": "Example Promoter",
"subdomain": "example"
}
}
Order-Payloads
Wird gesendet bei order.paid, order.refunded und order.cancelled. Das Feld action zeigt Dir, welcher Übergang ausgelöst wurde.
{
"action": "order.paid",
"created_at": "2026-04-22T12:34:56Z",
"account": { "name": "Example Promoter", "subdomain": "example" },
"eventable": {
"id": "ord_c3a9f4e1",
"order_code": "7K3Q9D2A",
"display_number": "7K3Q-9D2A",
"type": "Order",
"customer_email": "buyer@example.com",
"customer_name": "Jane Doe",
"customer_company": "Acme GmbH",
"customer_phone": "+49 30 1234567",
"total_amount": "42.00",
"net_amount": "35.29",
"vat_amount": "6.71",
"vat_rate": "19.0",
"currency": "EUR",
"status": "paid",
"paid_at": "2026-04-22T12:34:50Z",
"invoice_number": "INV-1-2026-00042",
"payment_provider": "stripe",
"payment_id": "pi_3OqXyz2eZvKYlo2C0AbCdEfG",
"attribution": {
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "spring-launch",
"utm_term": "concert tickets berlin",
"utm_content": "ad-variant-a",
"ref": "partner:radiox"
},
"b2b_invoice_requested": true,
"billing_address": {
"street": "Musterstr. 1",
"postal_code": "10115",
"city": "Berlin",
"country": "DE",
"vat_id": "DE123456789"
},
"items": [
{
"id": "oi_4d2a8b9c",
"check_in_code": "9M5V2H8C",
"display_check_in_code": "9M5V-2H8C",
"ticket_title": "General Admission",
"event_title": "Spring Showcase",
"price": "21.00"
}
]
}
}
| Feld | Typ | Hinweise |
|---|---|---|
eventable.id |
string | Öffentliche Bestellungs-ID. Stabil; kann als Korrelationsschlüssel gespeichert werden. |
eventable.order_code |
string | Menschenlesbarer Bestell-Code. Kann in kunden- oder teamseitigen Oberflächen angezeigt werden. |
eventable.display_number |
string | Formatierter Bestell-Code für die Anzeige, typischerweise gruppiert als XXXX-XXXX. |
eventable.customer_email |
string | E-Mail des Käufers. |
eventable.customer_name |
string | Name des Käufers. |
eventable.customer_company |
string | null | Firmenname, den der Käufer beim Checkout angegeben hat. null, wenn nicht angegeben. |
eventable.customer_phone |
string | null | Telefonnummer, die der Käufer beim Checkout angegeben hat. null, wenn nicht angegeben. |
eventable.total_amount |
string | Bruttogesamtbetrag, dezimal als String kodiert (z. B. "42.00"), um Float-Präzisionsprobleme zu vermeiden. |
eventable.net_amount |
string | Nettoanteil von total_amount, berechnet mit derselben Aufteilung wie auf der Kundenrechnung — beide Beträge stimmen auf den Cent überein. |
eventable.vat_amount |
string | MwSt.-Anteil von total_amount (total_amount − net_amount). |
eventable.vat_rate |
string | Effektiv angewendeter MwSt.-Satz als Prozent (z. B. "19.0", "7.7" oder "0.0" bei MwSt.-Befreiung). |
eventable.currency |
string | ISO-4217-Währungscode. |
eventable.status |
string | paid, refunded oder cancelled. |
eventable.paid_at |
string | null | ISO 8601 UTC. null bei Status ungleich paid. |
eventable.invoice_number |
string | null | Rechnungsnummer der Kundenrechnung, sobald die Rechnung erzeugt wurde. null bei order.cancelled und kurzzeitig zwischen Zahlung und Rechnungserstellung. |
eventable.payment_provider |
string | "stripe" oder "paypal" — welcher Anbieter diese Bestellung verarbeitet hat. |
eventable.payment_id |
string | null | Anbieter-spezifische Zahlungsreferenz. Bei Stripe die Payment-Intent-ID (pi_...) — füge sie ins Stripe-Dashboard ein, um die Zahlung zu finden. Bei PayPal die Capture-ID (oder die Order-ID vor dem Capture). null, solange die Bestellung nicht bezahlt ist. |
eventable.attribution |
object | Marketing-Attribution, beim Absenden der Bestellung erfasst. Immer vorhanden; leeres Objekt {}, wenn der Käufer nie mit Tracking-Parametern kam. Siehe Attribution. |
eventable.b2b_invoice_requested |
boolean | true, wenn der Käufer beim Checkout eine Geschäftsrechnung angefordert hat. |
eventable.billing_address |
object | Immer vorhanden. Einzelne Felder sind null, wenn der Käufer keine Geschäftsrechnung angefordert hat. Siehe Rechnungsadresse. |
eventable.items[].id |
string | Öffentliche ID des Bestellpostens (eine pro Ticket). |
eventable.items[].check_in_code |
string | Menschenlesbarer Ticket-Check-in-Code. Kann Team und Kunden angezeigt werden. |
eventable.items[].display_check_in_code |
string | Formatierter Check-in-Code für die Anzeige, typischerweise gruppiert als XXXX-XXXX. |
eventable.items[].ticket_title |
string | Titel des Tickettyps. |
eventable.items[].event_title |
string | Titel des Events, zu dem das Ticket gehört. |
eventable.items[].price |
string | Dezimalwert als String. |
Attribution
Das attribution-Objekt erfasst, woher der Käufer kam. UTM-Parameter und ref werden aus der URL der Checkout-Seite — und als Fallback aus dem Referer-Header der vorigen Seite — beim Absenden der Bestellung gelesen. Es gibt keine clientseitige Speicherung und kein Cookie-Banner: Die Daten reisen ausschließlich mit dem Formular mit. In der Praxis bedeutet das Last-Click-Attribution — die Quelle, die den Käufer zur Checkout-Seite gebracht hat, ist die, die gespeichert wird.
| Feld | Typ | Hinweise |
|---|---|---|
utm_source |
string | weggelassen | z. B. "google", "facebook", "newsletter". |
utm_medium |
string | weggelassen | z. B. "cpc", "email", "social". |
utm_campaign |
string | weggelassen | Kampagnenname aus der URL. |
utm_term |
string | weggelassen | Bezahltes Keyword, falls vorhanden. |
utm_content |
string | weggelassen | Anzeigen-/Creative-Variante, falls vorhanden. |
ref |
string | weggelassen | Frei wählbarer Referral-Code. Die Usetix-Shop-Footer-Links hängen ref=shop:<subdomain> an, wenn ein Käufer von einem Shop zur Marketingseite durchklickt — Registrierungen aus diesem Shop tragen so diese Quelle. |
Es werden nur tatsächlich erfasste Felder gesendet. Käufer ohne Tracking-Parameter erhalten "attribution": {}.
Rechnungsadresse
Wird gefüllt, wenn der Käufer beim Checkout eine Geschäftsrechnung angefordert hat (b2b_invoice_requested: true). Bei B2C-Bestellungen ist das Objekt vorhanden, alle Felder sind aber null.
| Feld | Typ | Hinweise |
|---|---|---|
street |
string | null | Straße und Hausnummer. |
postal_code |
string | null | Postleitzahl. |
city |
string | null | Stadt. |
country |
string | null | ISO-3166-1-Alpha-2-Ländercode (z. B. "DE", "CH", "AT"). |
vat_id |
string | null | EU-USt-IdNr., sofern angegeben. Wird beim Checkout für grenzüberschreitende EU-Bestellungen über VIES validiert. |
Event-Payloads
Wird gesendet bei event.published und event.unpublished.
{
"action": "event.published",
"created_at": "2026-04-22T12:34:56Z",
"account": { "name": "Example Promoter", "subdomain": "example" },
"eventable": {
"slug": "spring-showcase",
"type": "Event",
"title": "Spring Showcase",
"starts_at": "2026-05-01T19:00:00Z",
"ends_at": "2026-05-01T23:00:00Z",
"venue": {
"name": "The Venue",
"city": "Berlin"
}
}
}
| Feld | Typ | Hinweise |
|---|---|---|
eventable.slug |
string | URL-Slug. Die öffentliche URL des Events lautet https://<subdomain>.usetix.io/events/<slug>. |
eventable.title |
string | Titel des Events. |
eventable.starts_at |
string | ISO 8601 UTC. |
eventable.ends_at |
string | ISO 8601 UTC. |
eventable.venue.name |
string | Name des Veranstaltungsorts. |
eventable.venue.city |
string | Stadt des Veranstaltungsorts. |
Lokal testen
Für die lokale Entwicklung liefern Dir Tools wie ngrok oder Cloudflare Tunnel eine öffentliche URL, die auf localhost weiterleitet. Richte einen Webhook auf diese URL ein, löse dann Aktionen in Deinem Konto aus und siehe echte Payloads in Echtzeit ankommen.