PlanDocket
Fortgeschritten 15 min

Webhooks einrichten

Echtzeit-Benachrichtigungen für Ereignisse konfigurieren.

Voraussetzungen

  • Ein API-Schlüssel mit den erforderlichen Bereichen
  • Ein aktiver PlanDocket-Account
1

Webhook-Endpunkt erstellen

Erstellen Sie einen Endpunkt auf Ihrem Server, der POST-Anfragen empfangen kann.

2

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"]
  }'
3

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);
4

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"
    }
}
5

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);
6

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.submitted Bewerbung eingegangen Benötigter Scope: applications:read

    Ein 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.updated Bewerbung geändert Benötigter Scope: applications:read

    Die 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.approved Bewerbung genehmigt Benötigter Scope: applications:read

    Eine 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.rejected Bewerbung abgelehnt Benötigter Scope: applications:read

    Eine 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.created Kontakt erstellt Benötigter Scope: contacts:read

    Ein 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.updated Kontakt aktualisiert Benötigter Scope: contacts:read

    Die 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.deleted Kontakt gelöscht Benötigter Scope: contacts:read

    Ein 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.created Veranstaltung erstellt Benötigter Scope: events:read

    Eine 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.updated Veranstaltung geändert Benötigter Scope: events:read

    Daten, 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.deleted Veranstaltung gelöscht Benötigter Scope: events:read

    Eine 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.added Teilnehmer hinzugefügt Benötigter Scope: participants:read

    Ein 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.updated Teilnehmer geändert Benötigter Scope: participants:read

    Die 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.removed Teilnehmer entfernt Benötigter Scope: participants:read

    Ein 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.confirmed Teilnehmer bestätigt Benötigter Scope: participants:read

    Eine 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.created Rechnung erstellt Benötigter Scope: invoices:read

    Eine 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.sent Rechnung versendet Benötigter Scope: invoices:read

    Eine 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.overdue Rechnung überfällig Benötigter Scope: invoices:read

    Eine 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.paid Rechnung vollständig bezahlt Benötigter Scope: invoices:read

    Eine 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.cancelled Rechnung storniert Benötigter Scope: invoices:read

    Eine 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.created Gutschrift erstellt Benötigter Scope: invoices:read

    Eine 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.received Zahlung eingegangen Benötigter Scope: payments:read

    Eine 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.refunded Zahlung erstattet Benötigter Scope: payments:read

    Eine 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.assigned Stand zugewiesen Benötigter Scope: booths:read

    Ein 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.unassigned Stand freigegeben Benötigter Scope: booths:read

    Ein 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.requested Standreservierung angefragt Benötigter Scope: booths:read

    Ein 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.confirmed Standreservierung bestätigt Benötigter Scope: booths:read

    Eine 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.cancelled Standreservierung storniert Benötigter Scope: booths:read

    Eine 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.submitted Bestellung eingegangen Benötigter Scope: service_orders:read

    Ein 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.confirmed Bestellung bestätigt Benötigter Scope: service_orders:read

    Eine 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.completed Bestellung abgeschlossen Benötigter Scope: service_orders:read

    Eine 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.cancelled Bestellung storniert Benötigter Scope: service_orders:read

    Eine 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.submitted Einreichung eingegangen Benötigter Scope: submissions:read

    Ein 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.approved Einreichung angenommen Benötigter Scope: submissions:read

    Eine Einreichung wurde angenommen.

    Beispiel-Nutzdaten
    {
        "status": "approved",
        "submission_id": 23,
        "submission_type_id": 4,
        "event_participant_id": 7,
        "source": "admin"
    }
  • submission.rejected Einreichung abgelehnt Benötigter Scope: submissions:read

    Eine 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.signed Dokument unterschrieben Benötigter Scope: documents:read

    Ein Vertrag oder Dokument wurde unterschrieben.

    Beispiel-Nutzdaten
    {
        "document_id": 61,
        "title": "Ausstellervertrag",
        "event_participant_id": 7,
        "esign_status": "signed",
        "source": "admin"
    }
  • document.declined Unterschrift abgelehnt Benötigter Scope: documents:read

    Die Unterschrift eines Dokuments wurde abgelehnt.

    Beispiel-Nutzdaten
    {
        "esign_status": "declined",
        "document_id": 61,
        "title": "Ausstellervertrag",
        "event_participant_id": 7,
        "source": "admin"
    }

Newsletter

  • newsletter.subscribed Newsletter abonniert Benötigter Scope: newsletter:read

    Jemand hat den Newsletter bestätigt abonniert.

    Beispiel-Nutzdaten
    {
        "subscriber_id": 210,
        "email": "news@example.com",
        "language": "de",
        "source": "admin"
    }
  • newsletter.unsubscribed Newsletter abbestellt Benötigter Scope: newsletter:read

    Jemand hat den Newsletter abbestellt.

    Beispiel-Nutzdaten
    {
        "subscriber_id": 210,
        "email": "news@example.com",
        "language": "de",
        "source": "admin"
    }

Check-in

  • check_in.created Check-in erfasst Benötigter Scope: checkins:read

    Ein 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.created Listeneintrag hinzugefügt Benötigter Scope: lists:read

    Ein 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.updated Listeneintrag geändert Benötigter Scope: lists:read

    Ein 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.deleted Listeneintrag gelöscht Benötigter Scope: lists:read

    Ein 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"
    }
API Version v1.0