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
| Header | Example | What it is |
|---|---|---|
Calendly-Webhook-Signature | t=1767225600,v1=5257a869e7ecebeda32affa62cd… | Timestamp and HMAC-SHA256 signature, hex. Sent only if the subscription was created with a signing key. |
Content-Type | application/json | The 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
- Press Create webhook URL at the top of this page and copy the address.
- Create a personal access token in Calendly's integrations settings, and call
GET https://api.calendly.com/users/mewith it to read your user and organization URIs. - Send
POST https://api.calendly.com/webhook_subscriptionswith a JSON body containingurl(the address),eventssuch as["invitee.created","invitee.canceled"],organization,user,scopeset to"user", and a throwawaysigning_key. - Book a slot on one of your own event types.
- 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
Linear Webhook Example
A Linear webhook reports a change to one entity — an issue, a comment, a project — and, on updates, tells you the previous values of whatever changed.
Clerk Webhook Example
Clerk delivers its webhooks through Svix, so the three svix- headers and their signing scheme are shared with every other service that uses the same infrastructure.
Paddle Webhook Example
Paddle Billing notifications look a lot like Stripe's until you parse the signature header, which uses different separators and joins the timestamp to the body differently.
Razorpay Webhook Example
Razorpay wraps each event's entities inside payload under their own names, and signs the body with a secret you choose when creating the webhook.
Twitch Webhook Example
Twitch EventSub sends three kinds of message to the same URL, told apart by a header: a challenge to prove you own the endpoint, the notifications themselves, and a revocation when a subscription ends.
Bearer Tokens
How the Authorization header used to create the subscription works.
Curl POST JSON
Send a JSON body like this one from a terminal.
Webhook Tester
The inbox on its own page, plus a guide to signatures, retries and idempotent handlers.
All Webhook Examples
30 providers side by side: content type, signature scheme and retry behaviour.
Other webhooks with a timestamp in the signature
References
- Official Calendly documentation — The provider's own guide to the requests described on this page.
- Calendly Developer: create a webhook subscription — The API call that registers an endpoint.
- RFC 2104: HMAC — The keyed-hash construction this signature is built on.
- Node.js: crypto.timingSafeEqual() — The constant-time comparison used in the code on this page.
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.