Skip to content

Webhooks and email alerts

Instead of polling the changes feed, let us tell you. Set up webhook endpoints and email recipients in your dashboard; each one has its own calendars and its own choice of all changes or confirmations only.

Starter Pro Business Enterprise
Webhook endpoints — 3 20 Unlimited
Email recipients 2 5 20 50

Sent when an estimated holiday is confirmed, whether its dates stayed or moved. Islamic holidays such as Eid are estimated months ahead and confirmed by announcement; this is the event that tells you. A confirmation that arrives more than 7 days after the holiday began, such as a correction to a past year, appears only in dates.changed.

{
"id": "evt_9f3c1b2a4d5e6f70",
"type": "holiday.confirmed",
"created_at": "2027-03-08T19:42:10Z",
"release": { "id": "20270308T193500Z", "previous_id": "20270301T030000Z" },
"calendar": { "id": "SA", "name": "Saudi Arabia" },
"data": {
"name": "Eid al-Fitr",
"dates": ["2027-03-10", "2027-03-11", "2027-03-12"],
"previous_dates": ["2027-03-09", "2027-03-10", "2027-03-11"],
"moved": true
}
}

One per calendar per release, listing every date the release added, removed or modified, with the entry before and after (the same fields as the holidays endpoint). Confirmations appear here too. When a calendar is new in the release, its event also has "new_calendar": true and "holidays" (how many holidays it has): every entry is listed as added, but nothing about an existing calendar changed.

The dashboard’s Send test event sends one to a single endpoint straight away.

We POST each event as JSON to your URL with these headers:

Header Value
FC-Event-Id The event’s id; use it to ignore duplicates
FC-Event-Type holiday.confirmed, dates.changed or ping
FC-Signature t=<unix seconds>,v1=<hex HMAC-SHA256>

Answer with any 2xx within 10 seconds. Anything else, including a redirect, is a failure and is retried after 1, 5 and 15 minutes, then 1, 3, 6 and 12 hours. An endpoint that fails for 3 days is disabled and you get an email; turn it back on in the dashboard.

Events can arrive out of order. Each names its release: order by release.id, or simply refetch the calendar.

Compute HMAC-SHA256 of <t>.<raw body> with your endpoint’s signing secret (whsec_…, in the dashboard), compare it with v1 in constant time, and reject a t more than 5 minutes old.

Node:

import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}

Python:

import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))

Use the raw request body, before any JSON parsing.

Each recipient confirms their address from an email before alerts start, and every alert has a one-click unsubscribe. A recipient gets one email per release, leading with confirmations: “Eid al-Fitr confirmed in Saudi Arabia: 10 March 2027”.