Agent skill
Calendly Webhooks Skill
Receive and verify Calendly webhooks. Use when setting up Calendly webhook handlers, debugging Calendly signature verification, or handling scheduling events like invitee.created, invitee.canceled, invitee_no_show.created, or routing_form_submission.created.
Install this skill
npx skills add hookdeck/webhook-skills --skill calendly-webhooks
When to Use This Skill
- How do I receive Calendly webhooks?
- How do I verify Calendly webhook signatures?
- How do I handle
invitee.createdorinvitee.canceledevents? - Why is my Calendly webhook signature verification failing?
- Setting up Calendly webhook handlers and debugging replay protection
Verification (core)
Calendly signs each webhook with the Calendly-Webhook-Signature header, which contains a timestamp and a signature: t=<timestamp>,v1=<signature>. Compute HMAC-SHA256 (hex) over {timestamp}.{raw body} using the subscription's signing key, compare timing-safe, and reject stale timestamps (~3 min) to prevent replay. Calendly has no SDK verification helper — verify manually and always use the raw request body (don't JSON.parse first).
const crypto = require('crypto');
function verifyCalendlySignature(rawBody, header, signingKey, toleranceSec = 180) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const timestamp = parts.t;
const signature = parts.v1;
if (!timestamp || !signature) return false;
// Reject stale timestamps to prevent replay attacks
if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > toleranceSec) return false;
const expected = crypto
.createHmac('sha256', signingKey)
.update(`${timestamp}.${rawBody}`) // signed content = timestamp + "." + raw body
.digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'));
} catch {
return false; // length mismatch = invalid
}
}
For complete handlers with route wiring, event dispatch, and tests, see:
Common Event Types
| Event | Triggered When |
|---|---|
invitee.created | An invitee schedules an event |
invitee.canceled | An invitee cancels a scheduled event |
invitee_no_show.created | An invitee is marked as a no-show |
invitee_no_show.deleted | A no-show mark is removed from an invitee |
routing_form_submission.created | A routing form is submitted |
For the full event reference, see references/overview.md and Calendly's webhook documentation.
Environment Variables
# Signing key returned when you create the webhook subscription
CALENDLY_WEBHOOK_SIGNING_KEY=your_webhook_signing_key_here
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 calendly --path /webhooks/calendly
Reference Materials
- references/overview.md - What Calendly webhooks are, common events
- references/setup.md - Creating a webhook subscription, getting the signing key
- references/verification.md - Signature verification details and gotchas