CheckTheCal
← All guides

Webhooks

Send every change to your own server as JSON, signed so you can tell it really came from us.

A webhook sends every change we'd email about to a URL you choose, as a JSON POST. Use it to update a spreadsheet, post into another app, or trigger your own automation. Webhook deliveries never count toward your monthly email allowance.

Webhooks are part of the Pro and Business plans.

Add an endpoint

  1. Open a calendar and go to its Webhooks tab.
  2. Choose Webhook as the type and enter your URL. It must be https, and on a public host.
  3. Save. We show you a signing secret once. Copy it now and store it somewhere safe; we won't show it again.
  4. Choose Send a test to send a sample delivery to your endpoint straight away, rather than waiting for the calendar to change.

We don't follow redirects, so point the endpoint at its final URL.

What you receive

Each delivery is a JSON body like this:

{
  "id": "…",
  "apiVersion": "2026-08-01",
  "type": "change",
  "createdAt": "2026-10-02T14:02:00.000Z",
  "account": { "id": "…" },
  "watch": { "id": "…", "name": "School calendar" },
  "feed": { "id": "…", "url": "https://example.org/…", "calendarName": "School calendar" },
  "data": {
    "counts": { "added": 1, "changed": 1, "cancelled": 0 },
    "added": [ … ],
    "changed": [ … ],
    "cancelled": [ … ]
  }
}
  • type says what happened: change for calendar changes. We also send notices, such as a calendar we can't reach.
  • id is the same on every retry of the same delivery.
  • data has the same changes your email describes. Changed events list each field with its before and after values.
  • The feed's url has its private parts removed.
  • We may add new fields over time, so ignore any you don't recognise. If we ever make a breaking change, apiVersion will change.

Each request also carries these headers:

  • X-CheckTheCal-Event: the delivery type.
  • X-CheckTheCal-Delivery: an ID that stays the same across our retries. Use it to avoid processing the same delivery twice.
  • X-CheckTheCal-Signature: the signature, explained below.

Check the signature

Anyone can send a request to a public URL, so check the signature before trusting a delivery. The header looks like t=1790000000,v1=5257a8….

  1. Split the header on commas to get t (a Unix timestamp) and v1 (the signature).
  2. Take the raw request body, exactly as received, before any JSON parsing.
  3. Compute an HMAC-SHA256 of t, a full stop, then the raw body (<t>.<body>), using your signing secret as the key. Write the result as lowercase hex.
  4. Compare it to v1 using a constant-time comparison.
  5. Reject the request if t is more than 5 minutes from your server's current time. That stops anyone replaying an old request.

In Node.js:

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(secret, rawBody, header) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return expected.length === parts.v1?.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Retries and failures

Answer with any 2xx status to tell us a delivery arrived. If your endpoint errors or doesn't answer in time, we retry with a growing gap between attempts.

If an endpoint fails many times in a row, we turn it off and email you, so a broken integration doesn't fail quietly. It shows Turned off after failures on the Webhooks tab. Once it's fixed, choose Switch on there.

Every endpoint has a delivery log on the Webhooks tab: when each delivery was sent, the status your server answered with, and the start of its reply. Look there first when a delivery doesn't seem to arrive.

Still stuck? Browse all guides or email support@checkthecal.com.