Back to Overview

Webhook Events Reference

Full reference for Tymeslot webhook events: payloads, headers, retry behaviour and token verification.

Luka Breitig, Software Engineer & AI Developer
Luka Breitig

Aktualizováno 2. března 2026

Before You Begin

You will need:

  • A Tymeslot account with at least one meeting type set up
  • A Pro plan on tymeslot.app; self-hosted instances have no plan restriction
  • A publicly reachable HTTPS endpoint to receive deliveries. Tymeslot rejects plain HTTP URLs and addresses on private networks; a self-hosted instance can allow both by setting ALLOW_PRIVATE_IPS_FOR_WEBHOOKS=true.
  • Somewhere to store the security token Tymeslot generates for each webhook, so your endpoint can check it on every delivery

By the end of this guide, you will know how to set up a webhook, verify its deliveries and handle each event in your own application.

How Webhooks Work

Whenever a booking event you subscribed to happens, Tymeslot sends an HTTP POST with a JSON payload describing it to your endpoint URL. Before you start, know how deliveries behave:

  • Your endpoint must respond with a 2xx status code within 10 seconds. For slow work, respond with 200 OK straight away and process the payload in the background.
  • A failed delivery is retried with exponential backoff, up to 5 attempts in all, so your endpoint may receive the same event more than once.
  • Every delivery carries an X-Tymeslot-Delivery-Id header that stays the same across its retries. Use it to discard duplicates.
  • Every delivery attempt is listed under Dashboard → Automation → Webhooks → Logs with its HTTP status and any error.

1 Add a Webhook

  1. In Tymeslot, go to [Dashboard] → [Automation] → [Webhooks] and click [Create Webhook].
  2. Give the webhook a name in [Webhook Name] and enter your HTTPS endpoint in [Webhook URL].
  3. Under Event Subscriptions, select at least one event.
  4. Click [Create Webhook]. Tymeslot generates the security token. To copy it, click the edit (pencil) icon on the webhook card and use [Copy] next to Security Token, then store it where your endpoint can read it.
  5. Click [Test] on the webhook card to send a test request (event webhook.test) to your endpoint. Test calls are not logged; real deliveries appear under [Logs].
The Create Webhook form with Webhook Name and Webhook URL filled in, a note that the security token is generated automatically, and event subscriptions with Meeting Created and Meeting Cancelled ticked
Creating a webhook. The security token is generated once you save.

Event Types

A webhook receives only the events you subscribe to. The three booking-request events fire only on meeting types that need your approval; other meeting types never send them.

meeting.created

Meeting Created

Fired when a booking is confirmed. On a meeting type that needs your approval, it fires when you approve the request, not when the invitee submits it. It fires once per booking, so it is the event to drive CRM updates, chat notifications and similar automation. When the booking has a video room, the event waits for the room, so the join link is in the payload.

meeting.requested

Booking Requested

Fired when someone requests a slot on a meeting type that needs your approval. The slot is held until you approve or decline. The payload carries an approval object with requested_at, deadline_at and resolved_at.

meeting.declined

Booking Declined

Fired when you decline a booking request. The slot is free again, and the payload carries a decline object with declined_at and the reason you gave.

meeting.request_expired

Booking Request Expired

Fired when nobody answers a booking request before its deadline. The slot is free again.

meeting.cancelled

Meeting Cancelled

Fired when the host or the attendee cancels a meeting. The payload carries a cancellation object with cancelled_at and reason. Use it to notify people, update a CRM record or free up a resource.

meeting.rescheduled

Meeting Rescheduled

Fired when a meeting moves to a new date or time. The meeting keeps its id, and start_time and end_time hold the new time, so update your records by meeting id. The original time is not included, except for a group-meeting seat (see seat.previous below).

Request Headers

Every webhook delivery includes these headers:

X-Tymeslot-Token: <your-webhook-security-token>
X-Tymeslot-Timestamp: <ISO 8601 time the request was sent>
X-Tymeslot-Delivery-Id: <delivery-id, the same on every retry>
Content-Type: application/json
User-Agent: Tymeslot-Webhooks/1.0
X-Tymeslot-Token

The webhook's security token, sent as it is. Check it on every request before you process the payload, and reject anything that does not match.

X-Tymeslot-Timestamp

When Tymeslot sent this request, in ISO 8601 UTC. A retry carries its own, later timestamp.

X-Tymeslot-Delivery-Id

Identifies one delivery. It is the same on every retry of that delivery and different for every other delivery, so record the IDs you have processed and skip any you have seen before. Test requests do not carry it.

Content-Type

Always application/json. Parse the body as JSON.

User-Agent

Always Tymeslot-Webhooks/1.0. Useful for filtering access logs, but not a security check.

Payload Structure

All events use the same envelope. The event field identifies the type; the data field carries the event-specific payload:

{
  "event": "meeting.created",
  "timestamp": "2026-03-01T10:00:00Z",
  "webhook_id": "42",
  "data": {
    "meeting": {
      "id": "3f2b8c1e-9a4d-4e7b-b0c2-5d6e7f8a9b0c",
      "uid": "8d1e4a7c-2b3f-4c5d-9e6f-0a1b2c3d4e5f",
      "title": "30-Minute Consultation with Jane Smith",
      "summary": "30-Minute Consultation with Jane Smith",
      "description": null,
      "start_time": "2026-03-05T14:00:00Z",
      "end_time": "2026-03-05T14:30:00Z",
      "duration": 30,
      "status": "confirmed",
      "meeting_type": "30-Minute Consultation",
      "location": "Video call",
      "organizer": {
        "name": "Alex Johnson",
        "email": "alex@example.com",
        "title": null,
        "user_id": 7
      },
      "attendee": {
        "name": "Jane Smith",
        "email": "jane@example.com",
        "phone": null,
        "company": null,
        "timezone": "Europe/Berlin",
        "message": null
      },
      "guests": [],
      "urls": {
        "view": "https://tymeslot.yourdomain.com/alex/meeting/8d1e4a7c-2b3f-4c5d-9e6f-0a1b2c3d4e5f",
        "reschedule": "https://tymeslot.yourdomain.com/alex/meeting/8d1e4a7c-2b3f-4c5d-9e6f-0a1b2c3d4e5f/reschedule",
        "cancel": "https://tymeslot.yourdomain.com/alex/meeting/8d1e4a7c-2b3f-4c5d-9e6f-0a1b2c3d4e5f/cancel",
        "meeting": "https://meet.google.com/abc-defg-hij"
      },
      "video": {
        "enabled": true,
        "room_id": "abc-defg-hij",
        "organizer_url": "https://meet.google.com/abc-defg-hij",
        "attendee_url": "https://meet.google.com/abc-defg-hij",
        "created_at": "2026-03-01T10:00:00Z",
        "expires_at": null
      },
      "created_at": "2026-03-01T09:59:58Z",
      "updated_at": "2026-03-01T10:00:00Z"
    }
  }
}

Some keys appear only when they apply. approval is present on any booking that went through approval, including the meeting.created sent when you approve it. cancellation (cancelled_at, reason) is present on a cancelled meeting, and decline (declined_at, reason) replaces it when you declined a request. seat is present on group meetings only.

location is the location the booking is held at, as one line of text: the label the booker saw, with any address or phone number in parentheses, for example "Our office (12 High Street)". The video join link is not part of it; read it from urls.meeting, which is null when no video room was created.

seat appears only on group meetings, where several people book the same slot. Each person's booking is its own event: a seat booked fires meeting.created, a seat cancelled fires meeting.cancelled, a seat moved to another time fires meeting.rescheduled, and the host cancelling the whole slot fires one meeting.cancelled per remaining seat. In each, attendee and guests are that seat's booker and the guests they invited, and seat holds id (the seat's own id), capacity (the seats the slot offers), seats_taken (people booked on the slot, guests included, when the event fired) and previous. The meeting id is the slot's, shared by every seat on it.

A moved seat gets a new seat id, and usually a new meeting id; its meeting.rescheduled carries seat.previous (seat_id, meeting_id, start_time) naming the booking it replaced. On every other event previous is null.

All timestamps are ISO 8601 in UTC. To show a time the way the attendee saw it, convert it to attendee.timezone.

Verifying the Token

Compare the X-Tymeslot-Token header with your stored token before you process any payload. Use a constant-time string comparison: a plain equality check (===) is open to timing attacks, which let an attacker guess the token one character at a time.

Examples in common languages:

// Node.js
const crypto = require('crypto');

function isValidToken(received, expected) {
  const a = Buffer.from(received);
  const b = Buffer.from(expected);
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}
# Python
import hmac

def is_valid_token(received: str, expected: str) -> bool:
    return hmac.compare_digest(received, expected)
<?php
// PHP
function is_valid_token(string $received, string $expected): bool {
    return hash_equals($expected, $received);
}

Reject Requests With a Missing or Invalid Token

Return a 401 response straight away if the X-Tymeslot-Token header is missing or does not match, and do not process the payload. Without this check, anyone who finds your webhook URL can inject fake events into your system.

Handling Retries: Idempotency

Because Tymeslot retries failed deliveries, your endpoint may receive the same event more than once. A request that timed out on Tymeslot's side may still have reached you. Make your handler idempotent: processing the same delivery twice should have the same effect as processing it once.

The simplest approach is to store every X-Tymeslot-Delivery-Id you process successfully in a database or cache. Retries finish within minutes, so a 24-hour expiry is ample. On each request, check whether you have already processed the delivery ID. If you have, return 200 without running the handler again.

Store the ID After Processing, Not Before

The X-Tymeslot-Delivery-Id stays the same on every retry of a delivery. Record it once your handler has finished: if the handler fails partway through, the retry arrives with the same ID and you should process it again.

Retry Schedule

A delivery fails when your endpoint cannot be reached, does not respond within 10 seconds, or returns a status code outside 2xx. Tymeslot then retries it with exponential backoff, up to 5 attempts in all:

Attempt 1 Immediate
Attempt 2 About 1 second after the previous failure
Attempt 3 About 2 seconds after the previous failure
Attempt 4 About 4 seconds after the previous failure
Attempt 5 About 8 seconds after the previous failure

A response of 401, 403 or 404 is treated as a configuration problem and is not retried. After the fifth failed attempt, Tymeslot stops trying; the dashboard has no manual resend.

After 10 consecutive failed deliveries, Tymeslot disables the webhook and shows the reason on its card under [Dashboard] → [Automation] → [Webhooks]. Fix your endpoint, then switch the webhook back on; its failure count starts again from zero.

Tymeslot follows up to 5 redirects. A 301, 302 or 303 turns the request into a GET without the payload, and a redirect that changes the scheme, host or port (even http to https on the same host) drops the X-Tymeslot-Token header, so point the webhook at the final URL rather than one that redirects.

Delivery Log Retention

Webhook delivery logs are kept for 60 days and then deleted automatically. If you need a permanent audit trail, store the payloads in your own database when your endpoint processes them.

Delivery Logs

The delivery log is the first place to look when an integration misbehaves. To open it:

  1. Go to [Dashboard] → [Automation] → [Webhooks].
  2. Click [Logs] on the webhook you want to inspect.
  3. The totals at the top show how many deliveries succeeded and failed. Below them, each attempt is listed with its event type, the HTTP status your endpoint returned, the attempt number and the time.
  4. Where an attempt could not complete (a timeout, a refused connection or a certificate problem), the row shows the error message.

[Test] requests are not logged; the result appears on screen straight away.

Troubleshooting

Your endpoint receives empty GET requests instead of the payload

Your URL redirects with a 301, 302 or 303, which turns the POST into a GET without a body. Use the final destination URL, for example with or without the trailing slash or www your server expects.

All deliveries fail straight away

Check your endpoint's TLS certificate. Tymeslot rejects self-signed certificates and certificates from untrusted CAs, so use one from a public CA (Let's Encrypt, ZeroSSL or your cloud provider's managed certificate). The [Logs] entry usually shows the TLS error.

Deliveries succeed but events are processed twice

Deduplicate on the X-Tymeslot-Delivery-Id header as described under "Handling Retries" above: store the IDs you have processed and skip any that arrive again.

The connection test succeeds but real events do not arrive

[Test] sends its request directly, while real events go through a background job queue. First check that the webhook is subscribed to the event you expect and still shows as Active under [Dashboard] → [Automation] → [Webhooks]; after 10 consecutive failures it is disabled automatically. On a self-hosted instance, also check that the background job worker (Oban) is running.

❓ Frequently Asked Questions

How do I verify that a webhook request is genuinely from Tymeslot?

Tymeslot sends the webhook's security token unchanged in the X-Tymeslot-Token header on every delivery. On your endpoint, read that header and compare it with the token you stored. If they do not match, reject the request with a 401 and do not process the payload.

Use a constant-time comparison rather than a plain equality check. It stops attackers from working out your token character by character from how long the comparison takes:

// Node.js
const received = req.headers['x-tymeslot-token'];
const expected = process.env.WEBHOOK_SECRET;
const a = Buffer.from(received ?? '');
const b = Buffer.from(expected);
const valid = a.length === b.length && crypto.timingSafeEqual(a, b);
My webhook endpoint receives events but the token check always fails. Why?

Usually the token your endpoint compares against differs from the one Tymeslot holds for the webhook. Check for:

  • Leading or trailing whitespace: copy the token with the [Copy] button in the webhook's edit form rather than selecting it by hand.
  • A stale value: if you regenerated the token in Tymeslot, deploy the new value to your endpoint's environment as well.
  • A redirect to a different origin: Tymeslot drops the token header when a redirect changes the scheme, host or port, including a redirect from http to https on the same host, so use the final URL.

To test quickly, click [Test] and log the headers your endpoint receives.

Tymeslot is retrying webhook deliveries. How do I make it stop?

Tymeslot retries a delivery whenever your endpoint does not respond within 10 seconds or returns a status code other than 2xx, except 401, 403 and 404, which are treated as configuration problems and not retried. To stop retries, make sure your endpoint:

  • Returns 200 OK (or any other 2xx status) as its final response.
  • Responds within 10 seconds. If processing is slow, acknowledge the delivery with 200 straight away and process the payload in a background job.
  • Does not crash before it sends the response: an unhandled exception that closes the connection without a status code counts as a failure.
I need to process webhooks asynchronously. How should I handle this?

Acknowledge the webhook first, then process the event in a background job:

  1. Receive the request and verify the X-Tymeslot-Token header.
  2. Save the raw payload to a queue or database table.
  3. Return 200 OK before any slow work begins.
  4. Process the payload in a background worker at your own pace.

Tymeslot then never sees your endpoint as slow, and you have as long as you need for CRM updates or outbound API calls.

Are webhook payloads guaranteed to be delivered in order?

No. Tymeslot sends events as they happen, but a retried delivery can arrive after a later event. For example, a meeting.rescheduled event that needed three attempts may arrive after a meeting.cancelled event for the same meeting.

To handle out-of-order delivery:

  • Apply an event only when the meeting's updated_at is later than the version you already hold.
  • Use the X-Tymeslot-Delivery-Id header to skip deliveries you have already processed.
  • Model your data so that applying an older event on top of a newer state does nothing rather than overwriting it.

Verification Checklist

Confirm each item before you rely on your webhook integration:
  • Webhook added in [Dashboard] → [Automation] → [Webhooks] with an HTTPS endpoint URL and at least one event subscription
  • [Test] reports success for your endpoint
  • Your endpoint verifies X-Tymeslot-Token with a constant-time comparison before processing
  • Your endpoint stores X-Tymeslot-Delivery-Id values and skips duplicates
  • Your endpoint responds with 200 within 10 seconds (slow work runs in the background)
  • A real test booking was placed and the meeting.created event arrived at your endpoint with the expected payload
  • Your endpoint's TLS certificate is from a public CA (not self-signed)

🔗 Related Articles

Read n8n Automation

n8n Automation

Connect Tymeslot to n8n via webhooks for no-code automation. Trigger CRM updates, Slack notifications, and custom workflows on every booking event.