Skip to main content

Webhooks

Webhooks deliver real-time notifications when events happen on your hosted calendars, such as receiving a new invitation or getting an RSVP. No polling required — CalendarPipe POSTs the payload to your endpoint the moment the event occurs.

Pro Feature

Webhooks require a Pro plan and a hosted calendar. View plans and upgrade.

Registering a Webhook

Each hosted calendar has its own webhook. Register one with a PUT request:

PUT /api/v1/hosted-calendars/{id}/webhook
Authorization: Bearer `<api_key>`
Content-Type: application/json

{
"url": "https://your-agent.example.com/webhook",
"secret": "whsec_your-random-secret-here"
}

Rules:

  • url must start with https:// — plain HTTP endpoints are not accepted
  • secret must start with whsec_ — this prefix is a convention indicator that is stripped before use in HMAC computation

Response (200): Returns the updated webhook configuration.

Removing a Webhook

To stop receiving webhook deliveries for a hosted calendar:

DELETE /api/v1/hosted-calendars/{id}/webhook
Authorization: Bearer `<api_key>`

Response (204): No content.

Payload Format

CalendarPipe delivers a JSON payload to your endpoint for each event:

{
"type": "invitation.received",
"calendarId": "5e6ea455-...",
"organizerEmail": "organizer@example.com",
"organizerName": "Scheduling Bot",
"respondUrl": "https://www.calendarpipe.com/api/v1/hosted-calendars/{id}/invitations/{uid}/respond",
"event": {
"uid": "event-uid-here",
"title": "Strategy Session",
"start_at": "2026-03-25T10:00:00Z",
"end_at": "2026-03-25T11:00:00Z",
"attendees": [
{ "email": "alice@example.com", "displayName": "Alice" }
]
}
}
FieldDescription
typeEvent type identifier (see Event Types)
calendarIdID of the hosted calendar that received the event
organizerEmailEmail address of the invitation organizer
organizerNameDisplay name of the invitation organizer
respondUrlConvenience URL to respond to the invitation (see Responding via respondUrl)
event.uidUnique identifier for the calendar event
event.titleEvent title
event.start_atEvent start time in UTC ISO 8601 format
event.end_atEvent end time in UTC ISO 8601 format
event.attendeesArray of attendees with email and displayName

Webhook Headers

CalendarPipe follows the standard-webhooks specification. Every delivery includes these headers:

HeaderDescription
webhook-idUnique delivery ID for this specific webhook attempt
webhook-timestampUnix timestamp (seconds) of when the webhook was sent
webhook-signatureHMAC-SHA256 signature in the format v1,{base64}
info

CalendarPipe follows the standard-webhooks specification for all webhook delivery headers.

Verifying Signatures (HMAC)

Always verify the webhook signature before processing the payload. This ensures the request came from CalendarPipe and has not been tampered with.

Verification algorithm:

  1. Strip the whsec_ prefix from your webhook secret
  2. Construct the signed content string: {webhook-id}.{webhook-timestamp}.{body}
  3. Compute HMAC-SHA256 of the signed content using the stripped secret as the key
  4. Base64-encode the result
  5. Compare against the v1,{base64} entry in the webhook-signature header

Complete Node.js/TypeScript implementation:

import { createHmac } from 'crypto';

function verifyWebhook(
body: string,
headers: { 'webhook-id': string; 'webhook-timestamp': string; 'webhook-signature': string },
secret: string,
): boolean {
const strippedSecret = secret.replace(/^whsec_/, '');
const signedContent = `${headers['webhook-id']}.${headers['webhook-timestamp']}.${body}`;
const expected = createHmac('sha256', strippedSecret)
.update(signedContent)
.digest('base64');
const signatures = headers['webhook-signature'].split(' ');
return signatures.some((sig) => sig.startsWith('v1,') && sig.slice(3) === expected);
}

Use this function in your webhook handler:

app.post('/webhook', (req, res) => {
const body = req.rawBody; // Must be the raw request body string, not parsed JSON
const isValid = verifyWebhook(body, {
'webhook-id': req.headers['webhook-id'] as string,
'webhook-timestamp': req.headers['webhook-timestamp'] as string,
'webhook-signature': req.headers['webhook-signature'] as string,
}, process.env.WEBHOOK_SECRET!);

if (!isValid) {
return res.status(401).send('Invalid signature');
}

const payload = JSON.parse(body);
// Process payload...
res.status(200).send('OK');
});
Security

Never log or expose your whsec_ secret. Treat it like a password. If you suspect it has been compromised, update the webhook registration with a new secret.

info

The standardwebhooks npm package can also be used as an alternative to the raw crypto.createHmac implementation above. Both approaches are compatible with the CalendarPipe webhook format.

Event Types

Event TypeDescription
invitation.receivedA new invitation has arrived for your hosted calendar

Additional event types may be added in the future as CalendarPipe expands webhook coverage.

Responding via respondUrl

The webhook payload includes a respondUrl field. This is a convenience shortcut to accept, decline, or tentatively accept the invitation:

POST {respondUrl}
Authorization: Bearer `<api_key>`
Content-Type: application/json

{ "status": "ACCEPTED" }

This is equivalent to calling POST /api/v1/hosted-calendars/{id}/invitations/{uid}/respond directly. Valid values for status: ACCEPTED, DECLINED, TENTATIVE.

See the Invitations guide for full details on the invitation respond endpoint.

Troubleshooting

  • Endpoint must return 2xx -- CalendarPipe marks the delivery as failed if your endpoint returns any non-2xx status code
  • HTTPS required -- Webhook URLs must start with https://; http:// URLs are rejected at registration time
  • Strip whsec_ prefix in HMAC -- A common cause of verification failures is using the full whsec_... string as the HMAC key; the prefix must be stripped first
  • Use raw request body -- Parse the body string yourself after verification; passing a pre-parsed JSON object to the HMAC function will cause signature mismatches
  • Check webhook-timestamp for replay attacks -- Consider rejecting webhooks with timestamps older than 5 minutes to prevent replay attacks
info

These webhook endpoints may not yet appear in the interactive API Reference but are fully supported in production.