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.created or invitee.canceled events?
  • 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

EventTriggered When
invitee.createdAn invitee schedules an event
invitee.canceledAn invitee cancels a scheduled event
invitee_no_show.createdAn invitee is marked as a no-show
invitee_no_show.deletedA no-show mark is removed from an invitee
routing_form_submission.createdA 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