Skip to main content
Webhooks let you receive real-time HTTP POST notifications when events happen in Zeeg. Instead of polling the API, you register a callback URL and Zeeg pushes event data to you the moment something occurs. Zeeg supports four event types:
EventTrigger
invitee.scheduledAn invitee books, or a booking is rescheduled
invitee.cancelledAn invitee or host cancels a booking
routing_form.submittedA visitor submits a routing form
ai_agent.call_completed AlphaAn AI agent call reaches a terminal state

Setting up webhooks

Create a webhook subscription by sending a POST request to /webhooks.

Required fields

FieldTypeDescription
callbackUrlstringThe URL Zeeg will send events to. Must be publicly reachable.
eventsarrayOne or more event types to subscribe to.
scopestringuser or organization.

Optional fields

FieldTypeDescription
tokenstringA 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 a token 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

Both event types share the same payload structure. Cancellation-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"
  },
  "createdAt": "2026-04-10T08:30:00+00:00"
}

Key fields

FieldDescription
eventUuidUnique event identifier in zg-XXX format. Use this for storage and correlation.
inviteeUuidUnique invitee identifier in zg-XXX format. Use this to track a specific booking across schedule/cancel/reschedule events.
hostsObject mapping host positions to names, e.g. {"1": "Lena Meier"}.
hostsDetailsArray of host objects with slug, firstName, lastName, email, fullName.
inviteeStartAtThe event start time in the invitee’s timezone. Useful for display in confirmation emails or UIs.
cancelled / cancelledAt / cancelledByPopulated when the event is invitee.cancelled. cancelledBy indicates who initiated the cancellation.
rescheduled / rescheduledAt / rescheduleReasonPopulated when a booking is rescheduled.
oldEventUuid / newEventUuidWhen rescheduled, links the old and new events together.
price / currency / paymentGateway / transactionIdPopulated when the booking involves a paid event.
guestsArray 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

FieldDescription
routingFormIdUUID of the routing form.
reportIdUnique identifier for this specific submission.
routeTypeHow the visitor was routed (e.g., EVENT_TYPE).
isFallbackRoutetrue if the default/fallback route was used.
eventTypeThe event type the visitor was routed to (with id, title, slug, uri).
answers / questionsAndAnswersThe 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": [],
        "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

FieldDescription
conversationIdUnique call identifier. Use this as the dedup key — in rare cases the same call may be delivered more than once.
callOutcomeTerminal outcome: answered, voicemail, not_answered, busy, or unknown.
directioninbound (caller reached the agent) or outbound (agent dialled out).
transcriptOrdered conversation turns, each with role, message, and timeInCallSeconds. Tool-only turns with no spoken message are omitted.
conversationAnalysisAI-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).
actionsOrdered list of actions the agent executed. Each entry has a type, an optional routeId (the route that triggered it), and a calledAt timestamp.
bookingThe 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).
crmPersonIdUUID 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

OperationMethodEndpoint
List webhooksGET/webhooks/scope/{scope}
Get webhook detailsGET/webhooks/{uuid}
Delete webhookDELETE/webhooks/{uuid}
Replace {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.
When a webhook is auto-deleted, the subscription owner is notified by email. Monitor your inbox to avoid missing events silently.

Best practices

  1. Respond quickly. Return a 2xx status code as fast as possible. Zeeg expects a timely response from your callback URL.
  2. Process asynchronously. Queue the payload for background processing rather than doing heavy work inside the request handler.
  3. Use the verification token. Always set a token when creating a webhook and validate it on every incoming request.
  4. Handle duplicates idempotently. In rare cases, Zeeg may deliver the same event more than once. Use eventUuid or inviteeUuid to deduplicate.
  5. 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.
Last modified on June 2, 2026