| Event | Trigger |
|---|---|
invitee.scheduled | An invitee books, or a booking is rescheduled |
invitee.cancelled | An invitee or host cancels a booking |
invitee.withdrawn | An invitee exercises a statutory right of withdrawal on a paid booking. invitee.cancelled is also delivered for the same booking |
invitee.no_show | A host marks an invitee as a no-show |
routing_form.submitted | A visitor submits a routing form |
ai_agent.call_completed Alpha | An AI agent call reaches a terminal state |
Setting up webhooks
Create a webhook subscription by sending aPOST request to /webhooks.
Required fields
| Field | Type | Description |
|---|---|---|
callbackUrl | string | The URL Zeeg will send events to. Must be publicly reachable. |
events | array | One or more event types to subscribe to. |
scope | string | user or organization. |
Optional fields
| Field | Type | Description |
|---|---|---|
token | string | A secret token included in delivery headers for verification. |
Scope explained
user— You only receive events related to your own scheduling pages.organization— You receive events for all members of your organization. Requires an API token belonging to an admin or owner.
Example
curl -X POST https://api.zeeg.me/v2/webhooks \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"callbackUrl": "https://example.com/webhooks/zeeg",
"events": ["invitee.scheduled", "invitee.cancelled"],
"scope": "organization",
"token": "my-secret-verification-token"
}'
import requests
response = requests.post(
"https://api.zeeg.me/v2/webhooks",
headers={
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json",
},
json={
"callbackUrl": "https://example.com/webhooks/zeeg",
"events": ["invitee.scheduled", "invitee.cancelled"],
"scope": "organization",
"token": "my-secret-verification-token",
},
)
print(response.json())
const response = await fetch("https://api.zeeg.me/v2/webhooks", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
callbackUrl: "https://example.com/webhooks/zeeg",
events: ["invitee.scheduled", "invitee.cancelled"],
scope: "organization",
token: "my-secret-verification-token",
}),
});
const data = await response.json();
console.log(data);
$ch = curl_init("https://api.zeeg.me/v2/webhooks");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer YOUR_TOKEN",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"callbackUrl" => "https://example.com/webhooks/zeeg",
"events" => ["invitee.scheduled", "invitee.cancelled"],
"scope" => "organization",
"token" => "my-secret-verification-token",
]),
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Response
{
"success": true,
"resource": {
"uuid": "9a39bf60-a6c3-45e7-80cd-2cd36e520861",
"name": null,
"description": null,
"callbackUrl": "https://example.com/webhooks/zeeg",
"scope": "organization",
"creator": {
"firstName": "Lena",
"lastName": "Meier",
"slug": "lena-meier"
},
"events": ["invitee.scheduled", "invitee.cancelled"],
"organization": null,
"apiVersion": null,
"createdAt": "2026-04-10T08:30:00.000000Z",
"updatedAt": "2026-04-10T08:30:00.000000Z"
}
}
Verifying webhook origin
If you provide atoken when creating the webhook subscription, Zeeg sends it in the Token header of every webhook request. Use this value to verify that incoming requests actually come from Zeeg and not a third party.
Python
from flask import Flask, request, abort
WEBHOOK_TOKEN = "my-secret-verification-token"
app = Flask(__name__)
@app.route("/webhooks/zeeg", methods=["POST"])
def handle_webhook():
token = request.headers.get("Token")
if token != WEBHOOK_TOKEN:
abort(403)
payload = request.json
# Process the event
return "", 200
Event payloads
invitee.scheduled / invitee.cancelled / invitee.withdrawn / invitee.no_show
All four event types share the same payload structure. Cancellation-specific, no-show-specific and reschedule-specific fields are populated only when relevant.{
"event": "invitee.scheduled",
"eventUri": "https://api.zeeg.me/v2/scheduled-events/zg-O69bac566950c6",
"inviteeSalutation": "Ms.",
"inviteeFirstName": "Sophie",
"inviteeLastName": "Laurent",
"inviteeName": "Sophie Laurent",
"inviteeEmail": "sophie.laurent@northwind.io",
"inviteePhoneNumber": "+49 170 9876543",
"inviteeTimezone": "Europe/Paris",
"inviteeNumberOfSeats": 1,
"teamName": null,
"hosts": {
"1": "Lena Meier"
},
"hostsDetails": [
{
"slug": "lena-meier",
"firstName": "Lena",
"lastName": "Meier",
"email": "lena.meier@horizondigital.de",
"fullName": "Lena Meier"
}
],
"title": "30-Minute Discovery Call",
"duration": 30,
"durationPretty": "30 mins",
"type": "ONE_ON_ONE",
"eventTypeUri": "https://api.zeeg.me/v2/event-types/80f46bf5-eb01-4c07-960e-a9a3e18aae5e",
"singleUseLink": null,
"startAt": "2026-04-15T09:00:00+00:00",
"inviteeStartAt": "2026-04-15T11:00:00+02:00",
"inviteeStartAtPretty": "11:00 - Wednesday, April 15, 2026",
"endAt": "2026-04-15T09:30:00+00:00",
"inviteeEndAt": "2026-04-15T11:30:00+02:00",
"inviteeEndAtPretty": "11:30 - Wednesday, April 15, 2026",
"location": "Google Meet",
"locationLink": "https://meet.google.com/abc-defg-hij",
"questions": [
{
"question": "What would you like to discuss?",
"answer": "Product demo and pricing options"
}
],
"questionsAndAnswers": {
"What would you like to discuss?": "Product demo and pricing options"
},
"price": null,
"pricePerSeat": null,
"currency": null,
"paymentGateway": null,
"transactionId": null,
"guests": ["alex.chen@northwind.io"],
"eventUuid": "zg-O69bac566950c6",
"inviteeUuid": "zg-O69bad4047abf0",
"cancelUrl": "https://zeeg.me/cancel/zg-O69bad4047abf0",
"rescheduleUrl": "https://zeeg.me/rescheduling/zg-O69bad4047abf0",
"cancelled": false,
"cancelledAt": null,
"cancelledBy": null,
"cancellationReason": null,
"rescheduled": false,
"rescheduledAt": null,
"rescheduledBy": null,
"rescheduleReason": null,
"oldEventUri": null,
"oldEventUuid": null,
"oldInviteeUuid": null,
"oldStartAt": null,
"oldInviteeStartAt": null,
"newEventUri": null,
"newEventUuid": null,
"newInviteeUuid": null,
"newStartAt": null,
"newInviteeStartAt": null,
"utmCampaign": "spring_launch",
"utmSource": "linkedin",
"utmMedium": "social",
"utmTerm": null,
"utmContent": null,
"adAttribution": {
"gclid": "Cj0KCQjw_ndBhCrARIsAAy",
"fbclid": "IwAR2xq9Zt",
"landingUrl": "https://zeeg.me/lena-meier/30min?gclid=Cj0KCQjw_ndBhCrARIsAAy"
},
"withdrawal": {
"withdrawn": false,
"withdrawnAt": null,
"deadline": "2026-04-24T21:59:59+00:00",
"amountPaid": 2000,
"currency": "EUR"
},
"noShow": false,
"noShowAt": null,
"createdAt": "2026-04-10T08:30:00+00:00"
}
Key fields
| Field | Description |
|---|---|
eventUuid | Unique event identifier in zg-XXX format. Use this for storage and correlation. |
inviteeUuid | Unique invitee identifier in zg-XXX format. Use this to track a specific booking across schedule/cancel/reschedule events. |
hosts | Object mapping host positions to names, e.g. {"1": "Lena Meier"}. |
hostsDetails | Array of host objects with slug, firstName, lastName, email, fullName. |
inviteeStartAt | The event start time in the invitee’s timezone. Useful for display in confirmation emails or UIs. |
cancelled / cancelledAt / cancelledBy | Populated when the event is invitee.cancelled. cancelledBy indicates who initiated the cancellation. |
withdrawal | Present when the booking’s event type grants a statutory right of withdrawal, otherwise null. withdrawal.withdrawn distinguishes a withdrawal from an ordinary cancellation; withdrawal.amountPaid is in minor units. |
noShow / noShowAt | noShow is true once a host marks the invitee as a no-show; noShowAt carries the UTC timestamp of the marking. |
rescheduled / rescheduledAt / rescheduleReason | Populated when a booking is rescheduled. |
oldEventUuid / newEventUuid | When rescheduled, links the old and new events together. |
price / currency / paymentGateway / transactionId | Populated when the booking involves a paid event. |
guests | Array of additional guest email addresses added by the invitee. |
Store
inviteeUuid (and eventUuid) on your side. Invitee-related UUIDs are the best identifiers for correlating bookings, cancellations, and reschedules across webhook deliveries, and they ensure future compatibility — especially for Group events.routing_form.submitted
{
"event": "routing_form.submitted",
"reportId": "71621fb7-30b0-4d91-9f2e-a3e009cc6853",
"routingFormId": "f1cbafc4-b646-4cf9-af29-193491b555d9",
"routingFormName": "Inbound Lead Qualification",
"routingFormSlug": "inbound-lead-qual",
"routingFormUrl": "https://zeeg.me/RF/inbound-lead-qual",
"routeType": "EVENT_TYPE",
"routeCustomUrl": null,
"headline": null,
"isFallbackRoute": false,
"eventType": {
"id": "80f46bf5-eb01-4c07-960e-a9a3e18aae5e",
"title": "30-Minute Discovery Call",
"slug": "30min-discovery-call",
"uri": "https://api.zeeg.me/v2/event-types/80f46bf5-eb01-4c07-960e-a9a3e18aae5e"
},
"answers": [
{
"question": "What is your company size?",
"answer": "50-200 employees",
"inputId": "f7c82298-6dd9-43bd-98e4-c437b5c0ae47",
"order": 1
}
],
"questionsAndAnswers": {
"What is your company size?": "50-200 employees"
},
"utmCampaign": "spring_launch",
"utmSource": "linkedin",
"utmMedium": null,
"utmTerm": null,
"utmContent": null,
"adAttribution": {
"gclid": "Cj0KCQjw_ndBhCrARIsAAy",
"fbclid": "IwAR2xq9Zt",
"landingUrl": "https://zeeg.me/RF/company-size?gclid=Cj0KCQjw_ndBhCrARIsAAy"
},
"createdAt": "2026-04-10T08:30:00+00:00"
}
Key fields
| Field | Description |
|---|---|
routingFormId | UUID of the routing form. |
reportId | Unique identifier for this specific submission. |
routeType | How the visitor was routed (e.g., EVENT_TYPE). |
isFallbackRoute | true if the default/fallback route was used. |
eventType | The event type the visitor was routed to (with id, title, slug, uri). |
answers / questionsAndAnswers | The visitor’s responses. answers is an ordered array with inputId and order; questionsAndAnswers is a flat key-value map. |
ai_agent.call_completed
Alpha Fired once when an AI agent call reaches a terminal state (answered, voicemail, not answered, busy, or unknown). Contains the call outcome, transcript summary, any data the agent collected from the caller, actions the agent executed, and the booking created during the call (if any).{
"event": "ai_agent.call_completed",
"conversationId": "abc1234-0000-0000-0000-xyz987654321",
"agentId": "9a39bf60-a6c3-45e7-80cd-2cd36e520861",
"agentName": "Sales Agent",
"direction": "inbound",
"callOutcome": "answered",
"isCall": true,
"startedAt": "2026-06-01T10:00:00+00:00",
"endedAt": "2026-06-01T10:01:30+00:00",
"durationSeconds": 90,
"contactPhoneNumber": "+4917612345678",
"agentPhoneNumber": "+4989123456789",
"transcript": [
{ "role": "agent", "message": "Hello, thank you for calling. How can I help you today?", "timeInCallSeconds": 0 },
{ "role": "user", "message": "Hi, I'd like to know more about your pricing.", "timeInCallSeconds": 4 },
{ "role": "agent", "message": "Of course. Our plans start at 12 euros per month. Shall I book a follow-up call?", "timeInCallSeconds": 9 }
],
"conversationAnalysis": {
"transcriptSummary": "The caller asked about product pricing and requested a follow-up call next week.",
"collectedData": {
"name": { "value": "Alice Müller", "type": "string" },
"email": { "value": "alice@example.com", "type": "string" },
"message": { "value": "Please send me the pricing sheet.", "type": "string" },
"summary": { "value": "Caller asked about pricing and wants a follow-up.", "type": "string" }
}
},
"actions": [
{
"type": "book_event",
"routeId": null,
"calledAt": "2026-06-01T10:00:45+00:00"
},
{
"type": "send_email",
"routeId": "9b1c2d3e-4f56-7890-abcd-ef0123456789",
"calledAt": "2026-06-01T10:01:20+00:00"
}
],
"booking": {
"uri": "https://api.zeeg.me/v2/scheduled-events/zg-O69bd55e004c8b",
"uuid": "zg-O69bd55e004c8b",
"title": "30-Minute Meeting with Sales Agent",
"type": "ONE_ON_ONE",
"startTime": "2026-06-03T14:00:00.000000Z",
"endTime": "2026-06-03T14:30:00.000000Z",
"duration": 30,
"status": "confirmed",
"eventTypeUri": "https://api.zeeg.me/v2/event-types/80f46bf5-eb01-4c07-960e-a9a3e18aae5e",
"location": { "type": "google_meet", "joinUrl": "https://meet.google.com/abc-defg-hij" },
"maxActiveInvitees": 1,
"activeInviteesCount": 1,
"invitees": [
{
"uuid": "zg-A12cd34ef005a9c",
"salutation": null,
"fullName": "Alice Müller",
"email": "alice@example.com",
"guests": [],
"timeZone": "Europe/Berlin",
"cancellation": { "cancelledAt": null, "cancelledBy": null, "cancellerType": null, "cancellationReason": null },
"payment": null,
"questions": [],
"scheduledAt": "2026-06-01T10:00:50.000000Z",
"utm": { "utm_campaign": null, "utm_source": null, "utm_medium": null, "utm_content": null, "utm_term": null },
"customQueryParams": [],
"agentBookingReference": "123456"
}
],
"guests": [],
"hosts": [
{ "firstName": "Lena", "lastName": "Meier", "email": "lena.meier@example.com", "slug": "lena-meier", "url": "https://zeeg.me/lena-meier", "avatarUrl": null }
],
"createdAt": "2026-06-01T10:00:50.000000Z",
"updatedAt": "2026-06-01T10:00:50.000000Z",
"currentTime": "2026-06-01T10:01:30.000000Z"
},
"crmPersonId": "7f3e2a91-1234-4abc-9012-b3c456d78901"
}
Key fields
| Field | Description |
|---|---|
conversationId | Unique call identifier. Use this as the dedup key — in rare cases the same call may be delivered more than once. |
callOutcome | Terminal outcome: answered, voicemail, not_answered, busy, or unknown. |
direction | inbound (caller reached the agent) or outbound (agent dialled out). |
transcript | Ordered conversation turns, each with role, message, and timeInCallSeconds. Tool-only turns with no spoken message are omitted. |
conversationAnalysis | AI-generated analysis of the conversation. Contains transcriptSummary (narrative summary) and collectedData (a map of collected field id → { value, type }, e.g. name, email, message, summary, plus any custom fields). |
actions | Ordered list of actions the agent executed. Each entry has a type, an optional routeId (the route that triggered it), and a calledAt timestamp. |
booking | The scheduled-event resource created during the call (same shape as GET /v2/scheduled-events/{uuid}), or null if no booking was made. Each entry in invitees carries agentBookingReference — the 6-digit reference for AI-agent bookings (null for non-AI bookings). |
crmPersonId | UUID of the CRM person record matched to this call, if any. |
Use
conversationId to deduplicate deliveries and booking.uuid to correlate with invitee.scheduled webhook events for the same booking.Managing webhooks
| Operation | Method | Endpoint |
|---|---|---|
| List webhooks | GET | /webhooks/scope/{scope} |
| Get webhook details | GET | /webhooks/{uuid} |
| Delete webhook | DELETE | /webhooks/{uuid} |
{scope} with user or organization.
You can also manage webhooks from the Zeeg dashboard:
Account Settings > Webhooks
Auto-deletion
Zeeg automatically deletes a webhook subscription if any of the following conditions are met:
- DNS cannot be resolved for the callback URL.
- The callback URL returns a 404 Not Found response.
- The callback URL returns a 410 Gone response.
Best practices
- Respond quickly. Return a
2xxstatus code as fast as possible. Zeeg expects a timely response from your callback URL. - Process asynchronously. Queue the payload for background processing rather than doing heavy work inside the request handler.
- Use the verification token. Always set a
tokenwhen creating a webhook and validate it on every incoming request. - Handle duplicates idempotently. In rare cases, Zeeg may deliver the same event more than once. Use
eventUuidorinviteeUuidto deduplicate. - Monitor for auto-deletion emails. If your endpoint goes down and Zeeg deletes the subscription, you will only know through the notification email. Set up alerting on your side.