# Webhooks

Org owners and admins create endpoints (`orgs webhooks create --org <handle> --url https://...`, scope `workspace:admin`). The URL must be https on port 443 and public; redirects aren't followed. The secret (`whsec_...`) is shown once. `orgs webhooks test` sends a `webhook.test` event; `orgs webhooks deliveries` and `redeliver` show and repeat deliveries; `orgs webhooks rotate-secret` keeps the old secret signing for an overlap.

Each delivery is a JSON POST with `webhook-id`, `webhook-timestamp` (seconds) and `webhook-signature` headers (Standard Webhooks). To verify: compute HMAC-SHA256 with the base64-decoded part of the secret over `<webhook-id>.<webhook-timestamp>.<raw body>`, base64 it, and compare with each `v1,<signature>` in the header in constant time; reject timestamps more than 5 minutes off. Answer 2xx quickly; anything else is retried (5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 10 h), and 5 days of failure switches the endpoint off.

Envelope: `{ "id", "type", "version", "timestamp", "data" }`. Event types:

- `booking.created`: Someone booked a team's link. Sent when the time is reserved; a booking whose calendar event can't be made follows with booking.cancelled (reason calendar_error).
- `booking.rescheduled`: A booking moved to a new time, by the booker or the team. A move the calendar refused is reverted and sent again with the original time.
- `booking.cancelled`: A booking was cancelled. `reason` is booker_or_host (cancelled through Quorum), calendar_deleted (the event was deleted in the calendar) or calendar_error (the booking could not be put on the calendar).
- `booking.reassigned`: A pool member on a booking was replaced by someone else from the same pool.
- `poll.created`: A team poll was created.
- `poll.responded`: Someone answered a team's poll (or changed their answers). Respondent emails are included, as they are for the team's members.
- `poll.finalized`: A team poll was finalized at one of its times.
- `series.week_scheduled`: A week of a recurring poll series was scheduled: its meeting was booked, or kept or moved by the owner.
- `series.paused`: A series was paused. `reason` is owner, required_left, host_left or event_deleted.
- `link.created`: A booking link was created for a team.
- `link.updated`: A team's booking link was changed.
- `link.deleted`: A team's booking link was deleted. Its past and upcoming bookings stay.
- `member.joined`: Someone joined the org.
- `member.left`: Someone left the org, or was removed from it.
