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.
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:
urlmust start withhttps://— plain HTTP endpoints are not acceptedsecretmust start withwhsec_— 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" }
]
}
}
| Field | Description |
|---|---|
type | Event type identifier (see Event Types) |
calendarId | ID of the hosted calendar that received the event |
organizerEmail | Email address of the invitation organizer |
organizerName | Display name of the invitation organizer |
respondUrl | Convenience URL to respond to the invitation (see Responding via respondUrl) |
event.uid | Unique identifier for the calendar event |
event.title | Event title |
event.start_at | Event start time in UTC ISO 8601 format |
event.end_at | Event end time in UTC ISO 8601 format |
event.attendees | Array of attendees with email and displayName |
Webhook Headers
CalendarPipe follows the standard-webhooks specification. Every delivery includes these headers:
| Header | Description |
|---|---|
webhook-id | Unique delivery ID for this specific webhook attempt |
webhook-timestamp | Unix timestamp (seconds) of when the webhook was sent |
webhook-signature | HMAC-SHA256 signature in the format v1,{base64} |
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:
- Strip the
whsec_prefix from your webhook secret - Construct the signed content string:
{webhook-id}.{webhook-timestamp}.{body} - Compute HMAC-SHA256 of the signed content using the stripped secret as the key
- Base64-encode the result
- Compare against the
v1,{base64}entry in thewebhook-signatureheader
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');
});
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.
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 Type | Description |
|---|---|
invitation.received | A 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 fullwhsec_...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
These webhook endpoints may not yet appear in the interactive API Reference but are fully supported in production.