Skip to content

Webhooks

Syed Ahmer Shah edited this page Oct 25, 2026 · 1 revision

An organization admin can add webhooks in the organization's settings. HolidayOps then sends a signed POST to your URL whenever a calendar changes. Full design: docs/design/m4-subscriptions.md.

Events

calendar.updated, calendar.deleted, custom_day.created, custom_day.updated, custom_day.deleted, and ping (sent when you press "Send test").

{
  "id": "<delivery uuid>",
  "event": "custom_day.created",
  "created_at": "2027-03-01T09:00:00Z",
  "org": "acme",
  "data": {
    "calendar": {"id": "<uuid>", "name": "Karachi office", "slug": "karachi-office"},
    "custom_day": {"id": "<id>", "date": "2027-03-23", "name": "Office closed", "kind": "closure"}
  }
}

Headers

Header Value
X-HolidayOps-Event the event name
X-HolidayOps-Delivery a unique id per delivery; use it to ignore duplicates
X-HolidayOps-Signature t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">

Verify the signature

The secret starts with whsec_ and is shown once, when you create or rotate the webhook. Sign the raw request body, not a re-encoded copy, and reject timestamps older than 5 minutes.

import hashlib, hmac, time

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

Delivery rules

  • Your URL must be https on a public address; private and local addresses are refused.
  • Answer with any 2xx within 10 seconds. Redirects are not followed.
  • Network errors, 429 and 5xx are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours (6 attempts at most). Other 4xx answers fail at once.
  • After 20 failed deliveries in a row the webhook is turned off; turn it back on in settings.
  • The settings page shows the last 50 deliveries with their status and response.
  • An organization can have up to 10 webhooks. Admins and owners manage them.

Clone this wiki locally