← All help pages

    Webhooks: send your bookings to Airtable

    A webhook automatically sends every booking, reschedule and cancellation on your Calenoo pages to another tool: an Airtable base, or any tool that can receive an HTTPS webhook (Zapier, Make, n8n…). This page explains how to set it up, what Calenoo sends and what happens when something goes down.

    Last updated :

    What webhooks are for

    A webhook automatically sends the details of every booking, reschedule and cancellation to the web address of your choice, as soon as it happens.

    The tool that receives them can file them in a table, notify someone or start other actions. Calenoo only does the sending: the rest is set up in that tool.

    You set it up once, in your Settings, and it applies to all your booking pages. You can save up to 3 addresses.

    All you need is a Calenoo account: the “Webhooks” section is in your Settings, like booking pages.

    Webhooks combine with approving requests and group sessions, two other advanced features of booking pages. See the guide to the advanced features.

    Step by step: Airtable

    These steps follow Airtable's documentation of the “When webhook received” trigger (source, read on 2 November 2026). If Airtable's screen differs from what is described here, its help page prevails.

    1. In your Airtable base, open “Automations” and create an automation.
    2. Add a trigger and choose “When webhook received”. Airtable shows a unique web address: copy it.
    3. In Calenoo, open Settings › Webhooks, paste the address into “Receiving address” and click “Add”.
    4. Leave the “Sign deliveries” box unticked: Airtable cannot verify a signature.
    5. Click “Send a test”. Calenoo sends a complete sample booking, with no empty field, and shows “Test received” when Airtable accepts it.
    6. In Airtable, run the trigger's test (“Run test” button). Airtable uses the test delivery as the example for the next steps.
    7. Add the actions that will use this data and map the received fields to them. To find a rescheduled or cancelled booking again, use booking.id as the identifier.
    8. Test each step, then turn the automation on (from OFF to ON). From then on, every booking on your pages goes to Airtable.

    What Airtable says about this trigger

    Read on Airtable's help page on 2 November 2026. These limits are Airtable's and may change.

    • Only POST requests are accepted, with a Content-Type header set to application/json for JSON. That is what Calenoo sends.
    • A request must not exceed 100 kb, and the trigger accepts at most five requests per second.
    • Each delivery that starts an automation counts toward the automation run limits of your Airtable plan. The trigger is available on all plan types, with varying limits.
    • Airtable does not support signature verification: keep Calenoo's signature turned off for Airtable.
    • Anyone who knows the trigger's address can start your automation. Don't share it.

    The events sent

    Calenoo sends all the events in the table, with no way to select: your tool reads the event field and keeps the ones it needs.

    EventWhen it is sent
    booking.createdA guest books on a page without approval.
    booking.requestedA guest sends a request on an “approval required” page (status is pending, request_expires_at gives the deadline).
    booking.acceptedYou accept the request (status becomes confirmed, previous_status is pending; the video call link is added if there is one).
    booking.declinedYou decline the request (status is declined).
    booking.expiredThe request got no answer before the deadline (status is expired).
    booking.rescheduledThe guest moves their meeting or request (previous_start gives the old start; a request stays pending).
    booking.cancelledThe guest cancels their meeting or withdraws their request, or you cancel from Calenoo (previous_status gives the earlier status).

    Each delivery concerns a single booking. On a group page (several seats per slot), each participant gets a separate delivery, with their own booking.id and the booking.session_id of their session, which is the same for all its participants.

    The data format

    Each delivery is a POST request whose body is JSON (version 1). Here is an example. The “Send a test” button sends the same format, with test set to true and every field filled in.

    {
      "version": 1,
      "event": "booking.created",
      "event_id": "5d0c6b0e-6b0a-4a52-9d5c-2f1d3c9a7e11",
      "occurred_at": "2026-11-02T18:00:00Z",
      "test": false,
      "booking": {
        "id": "b3f1c2a4-8d7e-4c6b-9a10-5e2f4d8c1a77",
        "status": "confirmed",
        "previous_status": null,
        "start": "2026-11-07T09:00:00Z",
        "end": "2026-11-07T10:00:00Z",
        "start_local": "2026-11-07T10:00:00+01:00",
        "end_local": "2026-11-07T11:00:00+01:00",
        "timezone": "Europe/Zurich",
        "duration_minutes": 60,
        "previous_start": null,
        "location": "Bern",
        "meeting_url": null,
        "language": "de",
        "request_expires_at": null,
        "seats": null,
        "session_id": null,
        "invitee": {
          "name": "Jeanne Fit",
          "email": "jeanne@example.ch",
          "note": "Looking forward to it"
        }
      },
      "page": {
        "id": "9a4e7d52-3c1b-4f08-8e66-0b7d2a5c9f33",
        "slug": "personal-training",
        "title": "Personal Training"
      }
    }

    The main fields

    • event: the event (see the table above). event_id: an identifier shared by all addresses that receive the same event. occurred_at: the time of the event, in universal time (UTC).
    • booking.id: the booking's identifier. It is the key to use to find it again.
    • booking.status and booking.previous_status: the booking's status and its previous status (null if there is none).
    • booking.start and booking.end: start and end in universal time (UTC). booking.start_local and booking.end_local: the same times, with the local time and offset of the page's time zone (booking.timezone). booking.duration_minutes: the duration in minutes.
    • booking.previous_start: the old start, for a reschedule; null otherwise.
    • booking.location, booking.language and booking.meeting_url: the page's location, the booking's language and the video call link (null if there is none). A pending request has no link yet: it arrives with booking.accepted.
    • booking.request_expires_at: the deadline of a request awaiting approval (48 hours at most, never after the meeting starts), null otherwise. It stays filled in after the decision.
    • booking.seats: the number of seats per slot on a group page, null for a one-to-one meeting.
    • booking.session_id: the identifier of the session on a group page, the same for all its participants, null for a one-to-one meeting. It is kept after a cancellation; a reschedule gives the new session's identifier.
    • booking.invitee: the guest's name, email address and message (note, null if there is none).
    • page: the identifier, the short address (slug) and the title of the booking page.

    The headers

    • Content-Type: application/json
    • User-Agent: Calenoo-Webhook/1
    • Calenoo-Event: the event, for example booking.created.
    • Calenoo-Delivery: the delivery's identifier. It stays the same on every retry: use it to ignore a duplicate.
    • Calenoo-Signature: present only if signing is turned on (see below).

    The structure of version 1 does not change: any change would go through a version 2.

    Verifying the signature

    This step is optional. If you tick “Sign deliveries”, Calenoo adds a Calenoo-Signature header to every delivery: your tool can then check that the delivery really comes from Calenoo and hasn't been altered.

    The header looks like t=<seconds>,v1=<signature>. t is the sending time in seconds (Unix time). v1 is an HMAC-SHA256 in hexadecimal, computed with your secret over the text t.body: t, a dot, then the request body exactly as received.

    The secret starts with whsec_: use it in full, prefix included. Calenoo shows it only once, when it is created: copy it right away. If you lose it, create a new one with “New secret”.

    Here is an example in JavaScript (Node.js). Always compute the signature over the raw text received, before any JSON.parse. Also reject a delivery whose t is too old: here, more than 5 minutes.

    import crypto from "node:crypto";
    
    function isValidSignature(rawBody, header, secret) {
      const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
      const expected = crypto
        .createHmac("sha256", secret)
        .update(`${parts.t}.${rawBody}`)
        .digest("hex");
      const received = Buffer.from(parts.v1 ?? "", "hex");
      const computed = Buffer.from(expected, "hex");
      const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
      return fresh && received.length === computed.length && crypto.timingSafeEqual(received, computed);
    }

    Node.js lowercases header names: the header is read with req.headers['calenoo-signature'].

    When something goes down

    A delivery succeeds when your tool answers with a 2xx code within 10 seconds. Calenoo never reads the content of the answer and follows no redirect: a 3xx answer counts as a failure.

    • Answer 408, 425, 429 or 5xx, timeout or network drop: Calenoo tries again, 8 attempts at most (the first one included), after 1, 2, 5, 15 and 30 minutes, then 1 hour and 2 hours. If the answer contains a Retry-After header (in seconds) longer than that wait, Calenoo waits for it, up to 6 hours.
    • Any other 4xx answer (404 or 401, for example): final failure, no new attempt.
    • Deliveries to one address go out in order: a delivery waiting for a retry holds back the following ones.
    • After 5 final failures in a row, Calenoo disables the address and emails you. A successful delivery resets the counter to zero.
    • Missed deliveries are not sent again when you reactivate the address. Once the problem is fixed, click “Reactivate” in Settings › Webhooks.

    The same delivery can arrive twice, for example if your tool received it but its answer was lost. Since Calenoo-Delivery is the same on every attempt, your tool can ignore a duplicate.

    The “Latest deliveries” log in Settings › Webhooks shows each delivery's event, response code and status. It is kept for 30 days, without the content of the deliveries.

    Security and personal data

    • Calenoo sends only over https, on port 443. An address with a username or password, or on another port, is refused.
    • Private or local addresses (internal network, localhost…) are refused when saved, then checked again before every delivery.
    • Never in the deliveries: the links and tokens used to manage the booking, the IP fingerprint, your email address and the references of your calendar.
    • The content of each delivery is erased as soon as the delivery is finished.
    • You stay in control: you can pause an address, sign it, change its secret or delete it at any time.

    Each delivery contains personal data: the guest's name, email address and message, with the time of the booking. It goes to the tool you chose, which handles it under its own responsibility and its own terms. See the privacy policy.

    Frequently asked questions

    Do I need a paid Airtable plan?
    Airtable states that the “When webhook received” trigger is available on all plan types, with varying limits. Each delivery that starts an automation counts toward your plan's automation run limits: check them in your Airtable account (source).
    My tool is down: do I lose bookings?
    No, bookings stay in Calenoo. Only the delivery to your tool is affected: Calenoo tries up to 8 times, waiting from 1 minute to 2 hours. After 5 final failures in a row, the address is disabled and missed deliveries are not sent again.
    Can I receive only some of the events?
    No: Calenoo sends all events. Your tool reads the event field (or the Calenoo-Event header) and keeps the ones it wants to handle.
    How do I avoid duplicates in my table?
    Use booking.id as the key: a reschedule or a cancellation concerns the same booking, so it can update the same row. The Calenoo-Delivery header identifies each delivery and stays the same on every retry.
    Can I send to a server on my internal network or on another port?
    No. Calenoo only sends over HTTPS, on port 443, to a public address.