Calendly Webhook: Example Payload, Headers and Signature

Calendly webhooks are created through the API rather than a settings page, and they describe everything by URI. Here is a booking notification and the timestamped signature you get when you supply a signing key.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example below to your webhook URL from your browser. It is not sent by Calendly, it carries your browser's User-Agent, and its signature is a placeholder that will not verify. To see a real delivery, follow the steps further down.

Last updated . How this page is checked.

How the webhook URL works. It is a public address that records whatever is sent to it and answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is deleted 24 hours after it was created — or sooner if you press Delete URL or our server restarts, because requests are held in memory and never written to disk.

Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.

What is a Calendly webhook?

A Calendly webhook is an HTTP POST sent to a callback URL when a subscribed event occurs for a user or an organization: an invitee books a meeting (invitee.created), cancels one (invitee.canceled), is marked as a no-show, or submits a routing form. There is no dashboard screen for this; you create a webhook subscription with an API call and an access token.

The envelope is small: event names what happened, created_at says when, created_by is the user the subscription belongs to, and payload is the invitee. Calendly identifies resources by their API URI rather than by a bare id, so most references in the payload are full URLs you can request directly.

What a Calendly webhook request looks like

The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.

POST /your/webhook/path HTTP/1.1
Host: your-server.example
Calendly-Webhook-Signature: t=1767225600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Content-Type: application/json
{
  "created_at": "2026-01-01T12:00:00.000000Z",
  "created_by": "https://api.calendly.com/users/AAAAAAAAAAAAAAAA",
  "event": "invitee.created",
  "payload": {
    "uri": "https://api.calendly.com/scheduled_events/GBGBDCAADAEDCRZ2/invitees/CCCCCCCCCCCCCCCC",
    "name": "Ada Lovelace",
    "email": "ada@example.com",
    "status": "active",
    "timezone": "Europe/London",
    "rescheduled": false,
    "cancel_url": "https://calendly.com/cancellations/CCCCCCCCCCCCCCCC",
    "reschedule_url": "https://calendly.com/reschedulings/CCCCCCCCCCCCCCCC",
    "questions_and_answers": [
      {
        "question": "What would you like to cover?",
        "answer": "Webhook retries",
        "position": 0
      }
    ],
    "tracking": {
      "utm_source": "newsletter",
      "utm_campaign": null
    },
    "scheduled_event": {
      "uri": "https://api.calendly.com/scheduled_events/GBGBDCAADAEDCRZ2",
      "name": "30 Minute Meeting",
      "status": "active",
      "start_time": "2026-01-05T15:00:00.000000Z",
      "end_time": "2026-01-05T15:30:00.000000Z",
      "event_type": "https://api.calendly.com/event_types/BBBBBBBBBBBBBBBB",
      "location": {
        "type": "google_conference",
        "join_url": "https://meet.example.com/abc-defg-hij"
      }
    }
  }
}

Times are UTC; the invitee's own timezone is given separately so you can display the meeting as they saw it. The last path segment of a uri is the resource's UUID, which is what you store if you want a compact id. Answers to the booking form's custom questions are in questions_and_answers, in the order of position.

Headers Calendly sends

HeaderExampleWhat it is
Calendly-Webhook-Signaturet=1767225600,v1=5257a869e7ecebeda32affa62cd…Timestamp and HMAC-SHA256 signature, hex. Sent only if the subscription was created with a signing key.
Content-Typeapplication/jsonThe body is JSON.

Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.

How Calendly signs a webhook

The signing key is yours: you choose it and pass it as signing_key when creating the subscription. Calendly then builds the string {t}.{raw body}, where t is a Unix timestamp, computes an HMAC-SHA256 with your key, and sends both as t=…,v1=… in Calendly-Webhook-Signature. A subscription created without a key is delivered with no signature header.

After checking the HMAC, compare t with your clock and reject requests older than a few minutes, which is what stops a recorded request from being replayed. Compute it over the raw request body exactly as received — before any JSON parsing — and compare with a constant-time function.

const crypto = require('crypto');

// rawBody: string of the body exactly as received
function verifyCalendly(rawBody, header, signingKey, toleranceSec = 180) {
  const t = (String(header).match(/(?:^|,)t=(\d+)/) || [])[1];
  const v1 = (String(header).match(/(?:^|,)v1=([a-f0-9]+)/) || [])[1];
  if (!t || !v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false;

  const expected = crypto.createHmac('sha256', signingKey)
    .update(t + '.' + rawBody).digest('hex');
  return v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Retries and disabled subscriptions

Acknowledge with a 2xx. When an endpoint fails or times out, Calendly retries with exponential backoff for up to about a day, per its documentation at the time of writing. If deliveries are still failing at the end of that window the subscription is marked as disabled and stops sending until you create it again; its state is visible by listing webhook subscriptions through the API.

A reschedule is not a single event. The original booking is reported as invitee.canceled with rescheduled set to true, and the new time arrives as a separate invitee.created. A handler that treats every cancellation as a lost meeting will miscount, so check that flag first.

Catch a real Calendly webhook

  1. Press Create webhook URL at the top of this page and copy the address.
  2. Create a personal access token in Calendly's integrations settings, and call GET https://api.calendly.com/users/me with it to read your user and organization URIs.
  3. Send POST https://api.calendly.com/webhook_subscriptions with a JSON body containing url (the address), events such as ["invitee.created","invitee.canceled"], organization, user, scope set to "user", and a throwaway signing_key.
  4. Book a slot on one of your own event types.
  5. Read the notification in the inbox above, then delete the subscription with the API when you are finished.

Both API calls can be made from the request console on this site with an Authorization: Bearer header. Webhook subscriptions are not available on every Calendly plan; the API answers with a permission error if yours does not include them.

To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.

Things that catch people out

  • No signing key, no signature. The header only exists for subscriptions created with signing_key, and the key cannot be read back later.
  • References are URIs. Compare whole URIs or extract the trailing UUID consistently; mixing the two causes lookups to miss.
  • User scope and organization scope overlap. Two subscriptions that both cover the same booking each deliver it.

api-tester.com is independent and is not affiliated with Calendly. Providers change header names, limits and retry schedules; treat the official Calendly documentation as the source of truth.

Related examples and tools

Other webhooks with a timestamp in the signature

References

These links leave api-tester.com, which is independent of the companies and projects they point to.

Calendly webhook FAQ

What does a Calendly webhook payload look like?

A JSON object with created_at, created_by, event and payload. For invitee.created the payload is the invitee: name, email, timezone, status, answers to booking questions, cancel and reschedule URLs, and the scheduled event with its start and end time.

How do I verify a Calendly webhook signature?

Read t and v1 from the Calendly-Webhook-Signature header, compute an HMAC-SHA256 of t, a full stop and the raw body using your signing key, hex-encode it and compare with v1. Reject timestamps older than a few minutes.

How do I create a Calendly webhook?

Send a POST request to https://api.calendly.com/webhook_subscriptions with an access token, the callback url, the events to subscribe to, your organization URI and a scope of user or organization.

How does Calendly report a rescheduled meeting?

As two events: invitee.canceled for the old booking with rescheduled set to true, followed by invitee.created for the new one.

Does Calendly retry failed webhooks?

Yes, with exponential backoff for up to about a day according to Calendly's documentation at the time of writing. A subscription that is still failing after that is disabled.