Webhooks einrichten
Echtzeit-Benachrichtigungen für Ereignisse konfigurieren.
Voraussetzungen
- Ein API-Schlüssel mit den erforderlichen Bereichen
- Ein aktiver PlanDocket-Account
Webhook-Endpunkt erstellen
Erstellen Sie einen Endpunkt auf Ihrem Server, der POST-Anfragen empfangen kann.
Webhook registrieren
Registrieren Sie Ihren Webhook-Endpunkt über die API.
curl -X POST "https://ihr-mandant.plandocket.com/api/v1/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"events": ["participant.added", "invoice.paid"]
}'
Signatur validieren
Validieren Sie die Webhook-Signatur, um die Authentizität sicherzustellen.
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$secret = 'your_webhook_secret';
// Signature format: v1=<hmac>
if (!str_starts_with($signature, 'v1=')) {
http_response_code(401);
exit('Invalid signature format');
}
// Verify timestamp is recent (max 5 minutes)
if (!is_numeric($timestamp) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit('Timestamp expired');
}
$signedPayload = $timestamp . '.' . $payload;
$expected = hash_hmac('sha256', $signedPayload, $secret);
if (!hash_equals($expected, substr($signature, 3))) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
Nachricht auslesen
Jede Zustellung ist ein JSON-Umschlag: "id" (eindeutig pro Ereignis, bei jeder Wiederholung gleich), "event" (z. B. "invoice.paid"), "api_version", "created_at" (ISO 8601, UTC) und "data" (das geänderte Objekt). Zusätzlich kommen die Header X-Webhook-Id (= id, damit erkennen Sie Doppelte), X-Webhook-Delivery, X-Webhook-Attempt, X-Webhook-Timestamp und X-Webhook-Signature. Nutzdaten enthalten nie Passwörter, Tokens oder Dateiinhalte; unbekannte Felder bitte ignorieren, neue Felder können jederzeit hinzukommen.
{
"id": "6f1c2b0e-8a43-4d4e-9b8e-2f0c7d5a9e11",
"event": "invoice.paid",
"api_version": "2026-10",
"created_at": "2027-04-12T08:15:30+00:00",
"timestamp": "2027-04-12T08:15:30+00:00",
"data": {
"status": "paid",
"open_amount": 0,
"invoice_id": 311,
"invoice_number": "RE-2027-000311",
"contact_id": 42,
"total": 2380,
"currency": "EUR",
"due_date": "2027-04-30",
"source": "api"
}
}
Schnell antworten, Wiederholungen beachten
Antworten Sie innerhalb der Zeitgrenze (Standard 30 Sekunden) mit einem 2xx-Status und erledigen Sie die eigentliche Arbeit danach. Jede andere Antwort wird mit wachsendem Abstand wiederholt; "retry_count" ist die Zahl der Wiederholungen nach dem ersten Versuch. Weil eine Nachricht doppelt ankommen kann, merken Sie sich verarbeitete X-Webhook-Ids und überspringen Wiederholungen.
<?php
$event = json_decode($payload, true);
$webhookId = $_SERVER['HTTP_X_WEBHOOK_ID'] ?? $event['id'];
// Already processed (retry)? Answer 200 and stop.
if (alreadyProcessed($webhookId)) {
http_response_code(200);
exit;
}
markProcessed($webhookId);
queueForProcessing($event); // do the real work later
http_response_code(200);
Ereignisse auswählen
Ein API-Schlüssel kann nur Ereignisse abonnieren, deren Daten er lesen darf (z. B. "invoices:read" für Rechnungsereignisse); andere werden mit 422 im Feld "events" abgelehnt. GET /api/v1/webhooks/events listet alle Ereignisse mit dem nötigen Scope und ob Ihr Schlüssel sie abonnieren darf. Endpunkte erhalten keine Ereignisse mehr, wenn ihr API-Schlüssel abgeschaltet wird, abläuft, den Scope verliert oder sein Besitzer deaktiviert wird. Alle verfügbaren Ereignisse stehen unten.
curl -X GET "https://ihr-mandant.plandocket.com/api/v1/webhooks/events" \
-H "Authorization: Bearer YOUR_API_KEY"
Alle Webhook-Ereignisse
Diese Liste wird aus dem System erzeugt und ist daher immer vollständig. Das Beispiel zeigt den Teil "data" der Nachricht.
Bewerbungen
-
application.submittedBewerbung eingegangen Benötigter Scope:applications:readEin Unternehmen hat sich über das Bewerbungsformular beworben.
Beispiel-Nutzdaten
{ "application_id": 128, "event_id": 1, "company": "Holzbau Anna GmbH", "email": "anna@example.com", "status": "pending", "source": "admin" } -
application.updatedBewerbung geändert Benötigter Scope:applications:readDie Angaben einer Bewerbung wurden geändert.
Beispiel-Nutzdaten
{ "application_id": 128, "event_id": 1, "company": "Holzbau Anna GmbH", "email": "anna@example.com", "status": "pending", "source": "admin" } -
application.approvedBewerbung genehmigt Benötigter Scope:applications:readEine Bewerbung wurde angenommen; der Kontakt ist angelegt.
Beispiel-Nutzdaten
{ "status": "approved", "contact_id": 42, "application_id": 128, "event_id": 1, "company": "Holzbau Anna GmbH", "email": "anna@example.com", "source": "admin" } -
application.rejectedBewerbung abgelehnt Benötigter Scope:applications:readEine Bewerbung wurde abgelehnt.
Beispiel-Nutzdaten
{ "status": "rejected", "application_id": 128, "event_id": 1, "company": "Holzbau Anna GmbH", "email": "anna@example.com", "source": "admin" }
Kontakte
-
contact.createdKontakt erstellt Benötigter Scope:contacts:readEin neuer Kontakt wurde angelegt (von Hand, per Import oder über die API).
Beispiel-Nutzdaten
{ "contact_id": 42, "company": "Holzbau Anna GmbH", "first_name": "Anna", "last_name": "Holz", "email": "anna@example.com", "source": "admin" } -
contact.updatedKontakt aktualisiert Benötigter Scope:contacts:readDie Daten eines Kontakts wurden geändert.
Beispiel-Nutzdaten
{ "contact_id": 42, "company": "Holzbau Anna GmbH", "first_name": "Anna", "last_name": "Holz", "email": "anna@example.com", "source": "admin" } -
contact.deletedKontakt gelöscht Benötigter Scope:contacts:readEin Kontakt wurde in den Papierkorb verschoben.
Beispiel-Nutzdaten
{ "contact_id": 42, "company": "Holzbau Anna GmbH", "first_name": "Anna", "last_name": "Holz", "email": "anna@example.com", "source": "admin" }
Veranstaltungen
-
event.createdVeranstaltung erstellt Benötigter Scope:events:readEine neue Veranstaltung wurde angelegt.
Beispiel-Nutzdaten
{ "event_id": 1, "title": "Nordmesse 2027", "starts_at": "2027-05-12T09:00:00+02:00", "ends_at": "2027-05-13T17:00:00+02:00", "city": "Hamburg", "source": "admin" } -
event.updatedVeranstaltung geändert Benötigter Scope:events:readDaten, Termine oder Fristen einer Veranstaltung wurden geändert.
Beispiel-Nutzdaten
{ "event_id": 1, "title": "Nordmesse 2027", "starts_at": "2027-05-12T09:00:00+02:00", "ends_at": "2027-05-13T17:00:00+02:00", "city": "Hamburg", "source": "admin" } -
event.deletedVeranstaltung gelöscht Benötigter Scope:events:readEine Veranstaltung wurde in den Papierkorb verschoben.
Beispiel-Nutzdaten
{ "event_id": 1, "title": "Nordmesse 2027", "starts_at": "2027-05-12T09:00:00+02:00", "ends_at": "2027-05-13T17:00:00+02:00", "city": "Hamburg", "source": "admin" }
Teilnehmer
-
participant.addedTeilnehmer hinzugefügt Benötigter Scope:participants:readEin Kontakt wurde als Teilnehmer einer Veranstaltung hinzugefügt.
Beispiel-Nutzdaten
{ "event_participant_id": 7, "event_id": 1, "contact_id": 42, "category_id": 3, "is_confirmed": true, "source": "admin" } -
participant.updatedTeilnehmer geändert Benötigter Scope:participants:readDie Teilnahmedaten wurden geändert.
Beispiel-Nutzdaten
{ "event_participant_id": 7, "event_id": 1, "contact_id": 42, "category_id": 3, "is_confirmed": true, "source": "admin" } -
participant.removedTeilnehmer entfernt Benötigter Scope:participants:readEin Teilnehmer wurde von einer Veranstaltung entfernt.
Beispiel-Nutzdaten
{ "event_participant_id": 7, "event_id": 1, "contact_id": 42, "category_id": 3, "is_confirmed": true, "source": "admin" } -
participant.confirmedTeilnehmer bestätigt Benötigter Scope:participants:readEine Teilnahme wurde bestätigt.
Beispiel-Nutzdaten
{ "event_participant_id": 7, "event_id": 1, "contact_id": 42, "category_id": 3, "is_confirmed": true, "source": "admin" }
Rechnungen und Gutschriften
-
invoice.createdRechnung erstellt Benötigter Scope:invoices:readEine Rechnung wurde erstellt.
Beispiel-Nutzdaten
{ "invoice_id": 311, "invoice_number": "RE-2027-000311", "contact_id": 42, "total": 2380, "currency": "EUR", "status": "sent", "due_date": "2027-04-30", "source": "admin" } -
invoice.sentRechnung versendet Benötigter Scope:invoices:readEine Rechnung wurde per E-Mail versendet.
Beispiel-Nutzdaten
{ "invoice_id": 311, "invoice_number": "RE-2027-000311", "contact_id": 42, "total": 2380, "currency": "EUR", "status": "sent", "due_date": "2027-04-30", "source": "admin" } -
invoice.overdueRechnung überfällig Benötigter Scope:invoices:readEine Rechnung ist nach dem Fälligkeitsdatum noch offen (einmal je Rechnung).
Beispiel-Nutzdaten
{ "status": "overdue", "days_overdue": 7, "invoice_id": 311, "invoice_number": "RE-2027-000311", "contact_id": 42, "total": 2380, "currency": "EUR", "due_date": "2027-04-30", "source": "admin" } -
invoice.paidRechnung vollständig bezahlt Benötigter Scope:invoices:readEine Rechnung ist vollständig bezahlt. Teilzahlungen lösen nur „Zahlung eingegangen“ aus.
Beispiel-Nutzdaten
{ "status": "paid", "open_amount": 0, "invoice_id": 311, "invoice_number": "RE-2027-000311", "contact_id": 42, "total": 2380, "currency": "EUR", "due_date": "2027-04-30", "source": "admin" } -
invoice.cancelledRechnung storniert Benötigter Scope:invoices:readEine Rechnung wurde storniert.
Beispiel-Nutzdaten
{ "status": "cancelled", "credit_note_id": 312, "invoice_id": 311, "invoice_number": "RE-2027-000311", "contact_id": 42, "total": 2380, "currency": "EUR", "due_date": "2027-04-30", "source": "admin" } -
credit_note.createdGutschrift erstellt Benötigter Scope:invoices:readEine Gutschrift oder Stornorechnung wurde erstellt.
Beispiel-Nutzdaten
{ "invoice_id": 312, "invoice_number": "GS-2027-000012", "cancelled_invoice_id": 311, "total": -2380, "status": "sent", "contact_id": 42, "currency": "EUR", "due_date": "2027-04-30", "source": "admin" }
Zahlungen
-
payment.receivedZahlung eingegangen Benötigter Scope:payments:readEine Zahlung wurde erfasst – auch Teilzahlungen und Online-Zahlungen.
Beispiel-Nutzdaten
{ "payment_id": 95, "invoice_id": 311, "amount": 1190, "currency": "EUR", "method": "bank_transfer", "open_amount": 1190, "source": "admin" } -
payment.refundedZahlung erstattet Benötigter Scope:payments:readEine Zahlung wurde ganz oder teilweise erstattet.
Beispiel-Nutzdaten
{ "refund_id": 9, "refunded_amount": 1190, "payment_id": 95, "invoice_id": 311, "amount": 1190, "currency": "EUR", "method": "bank_transfer", "open_amount": 1190, "source": "admin" }
Stände und Reservierungen
-
booth.assignedStand zugewiesen Benötigter Scope:booths:readEin Stand wurde einem Teilnehmer zugewiesen.
Beispiel-Nutzdaten
{ "booth_id": 17, "booth_number": "A01", "plan_id": 2, "event_id": 1, "event_participant_id": 7, "source": "admin" } -
booth.unassignedStand freigegeben Benötigter Scope:booths:readEin Stand wurde wieder freigegeben.
Beispiel-Nutzdaten
{ "booth_id": 17, "booth_number": "A01", "plan_id": 2, "event_id": 1, "event_participant_id": 7, "source": "admin" } -
booth_reservation.requestedStandreservierung angefragt Benötigter Scope:booths:readEin Aussteller hat im Portal einen Stand reserviert.
Beispiel-Nutzdaten
{ "reservation_id": 33, "status": "requested", "booth_id": 17, "booth_number": "A01", "plan_id": 2, "event_id": 1, "event_participant_id": 7, "source": "admin" } -
booth_reservation.confirmedStandreservierung bestätigt Benötigter Scope:booths:readEine Standreservierung wurde bestätigt.
Beispiel-Nutzdaten
{ "reservation_id": 33, "status": "confirmed", "booth_id": 17, "booth_number": "A01", "plan_id": 2, "event_id": 1, "event_participant_id": 7, "source": "admin" } -
booth_reservation.cancelledStandreservierung storniert Benötigter Scope:booths:readEine Standreservierung wurde storniert.
Beispiel-Nutzdaten
{ "reservation_id": 33, "status": "cancelled", "booth_id": 17, "booth_number": "A01", "plan_id": 2, "event_id": 1, "event_participant_id": 7, "source": "admin" }
Leistungsbestellungen
-
service_order.submittedBestellung eingegangen Benötigter Scope:service_orders:readEin Aussteller hat Leistungen bestellt.
Beispiel-Nutzdaten
{ "status": "submitted", "service_order_id": 54, "order_number": "SO-2027-0054", "event_participant_id": 7, "total": 450, "currency": "EUR", "source": "admin" } -
service_order.confirmedBestellung bestätigt Benötigter Scope:service_orders:readEine Bestellung wurde bestätigt.
Beispiel-Nutzdaten
{ "status": "confirmed", "service_order_id": 54, "order_number": "SO-2027-0054", "event_participant_id": 7, "total": 450, "currency": "EUR", "source": "admin" } -
service_order.completedBestellung abgeschlossen Benötigter Scope:service_orders:readEine Bestellung wurde abgeschlossen.
Beispiel-Nutzdaten
{ "status": "completed", "service_order_id": 54, "order_number": "SO-2027-0054", "event_participant_id": 7, "total": 450, "currency": "EUR", "source": "admin" } -
service_order.cancelledBestellung storniert Benötigter Scope:service_orders:readEine Bestellung wurde storniert.
Beispiel-Nutzdaten
{ "status": "cancelled", "service_order_id": 54, "order_number": "SO-2027-0054", "event_participant_id": 7, "total": 450, "currency": "EUR", "source": "admin" }
Einreichungen
-
submission.submittedEinreichung eingegangen Benötigter Scope:submissions:readEin Aussteller hat etwas eingereicht (z. B. Logo oder Katalogtext).
Beispiel-Nutzdaten
{ "submission_id": 23, "submission_type_id": 4, "event_participant_id": 7, "status": "submitted", "source": "admin" } -
submission.approvedEinreichung angenommen Benötigter Scope:submissions:readEine Einreichung wurde angenommen.
Beispiel-Nutzdaten
{ "status": "approved", "submission_id": 23, "submission_type_id": 4, "event_participant_id": 7, "source": "admin" } -
submission.rejectedEinreichung abgelehnt Benötigter Scope:submissions:readEine Einreichung wurde abgelehnt oder zur Überarbeitung zurückgegeben.
Beispiel-Nutzdaten
{ "status": "rejected", "submission_id": 23, "submission_type_id": 4, "event_participant_id": 7, "source": "admin" }
Dokumente und Verträge
-
document.signedDokument unterschrieben Benötigter Scope:documents:readEin Vertrag oder Dokument wurde unterschrieben.
Beispiel-Nutzdaten
{ "document_id": 61, "title": "Ausstellervertrag", "event_participant_id": 7, "esign_status": "signed", "source": "admin" } -
document.declinedUnterschrift abgelehnt Benötigter Scope:documents:readDie Unterschrift eines Dokuments wurde abgelehnt.
Beispiel-Nutzdaten
{ "esign_status": "declined", "document_id": 61, "title": "Ausstellervertrag", "event_participant_id": 7, "source": "admin" }
Newsletter
Check-in
-
check_in.createdCheck-in erfasst Benötigter Scope:checkins:readEin Teilnehmer wurde am Einlass eingecheckt.
Beispiel-Nutzdaten
{ "check_in_id": 880, "event_participant_id": 7, "event_id": 1, "checked_in_at": "2027-05-12T08:41:12+02:00", "type": "scan", "source": "admin" }
Listen
-
list_entry.createdListeneintrag hinzugefügt Benötigter Scope:lists:readEin Eintrag wurde zu einer Liste hinzugefügt (z. B. Standpersonal).
Beispiel-Nutzdaten
{ "list_entry_id": 12, "list_template_id": 3, "event_participant_id": 7, "values": { "name": "Tom Zimmer", "funktion": "verkauf" }, "source": "admin" } -
list_entry.updatedListeneintrag geändert Benötigter Scope:lists:readEin Listeneintrag wurde geändert.
Beispiel-Nutzdaten
{ "list_entry_id": 12, "list_template_id": 3, "event_participant_id": 7, "values": { "name": "Tom Zimmer", "funktion": "verkauf" }, "source": "admin" } -
list_entry.deletedListeneintrag gelöscht Benötigter Scope:lists:readEin Listeneintrag wurde gelöscht.
Beispiel-Nutzdaten
{ "list_entry_id": 12, "list_template_id": 3, "event_participant_id": 7, "values": { "name": "Tom Zimmer", "funktion": "verkauf" }, "source": "admin" }