Webhook events
Every event type emitted in production, with the exact payload your endpoint will receive for each.
If an event is not on this page, it does not exist.
Event catalog
All current events belong to the meetings domain:
| Event | Fired when |
|---|---|
meeting.started | A meeting transitions to in-progress |
meeting.ended | A meeting completes |
meeting.cancelled | A meeting is cancelled |
participant.joined | A participant joins a meeting |
participant.left | A participant leaves a meeting |
recording.ready | A meeting recording becomes available |
transcript.ready | A meeting transcript becomes available |
Subscriptions are per endpoint: each registered endpoint declares which of these event types it receives. See webhooks for delivery mechanics.
Envelope
Every event shares the same envelope; only data varies. All six top-level fields are always present:
{
"id": "evt-uuid",
"type": "meeting.started",
"timestamp": "2026-06-12T10:00:00Z",
"delivery_id": "delivery-uuid",
"organization_id": "org-uuid",
"data": { }
}| Field | Type | Meaning |
|---|---|---|
id | UUID string | Unique identifier for this event occurrence. |
type | string | The event type, one of the seven listed above. Also repeated in the X-Webhook-Event header. |
timestamp | ISO 8601 string | When the event was produced, in UTC. |
delivery_id | UUID string | Unique per delivery attempt's target. Deduplicate on this — it is also sent as X-Webhook-Delivery-ID. |
organization_id | UUID string | The organization the event belongs to. |
data | object | The per-event body documented below. |
The body is signed as-sent: X-Webhook-Signature is the hex HMAC-SHA256 of the exact JSON bytes shown. See webhooks for verification and authentication for a constant-time check.
Payloads by event
Each example below is a complete delivery body — the shared envelope wrapping that event's data.
meeting.started / meeting.ended / meeting.cancelled
The three meeting lifecycle events carry an identical data shape; only type differs.
{
"id": "9f1c0b3a-7d2e-4a51-9b6f-2c8e0d4a1f33",
"type": "meeting.ended",
"timestamp": "2026-06-12T11:00:04Z",
"delivery_id": "c4a8e2f0-1b6d-4e90-8a3c-5f7b9d0e1a22",
"organization_id": "1d7e5c9a-3b2f-4e80-9c1a-6d8f0b2e4a55",
"data": {
"meeting_id": "7a2b4c6d-8e0f-4a12-9b34-5c6d7e8f9a01",
"title": "Final interview — Senior Engineer",
"scheduled_start": "2026-06-12T10:00:00Z",
"scheduled_end": "2026-06-12T11:00:00Z",
"host_id": "3e5f7a9b-1c2d-4e60-8f01-2a3b4c5d6e70"
}
}data field | Type | Notes |
|---|---|---|
meeting_id | UUID string | The meeting this event concerns. |
title | string | The meeting title at the time of the event. |
scheduled_start | ISO 8601 string | Planned start time. |
scheduled_end | ISO 8601 string | Planned end time. |
host_id | UUID string | The user who owns the meeting. |
participant.joined / participant.left
Both participant events share one data shape; type distinguishes a join from a leave.
{
"id": "2b8d4f60-9a1c-4e23-8b50-7d9e1f3a5c66",
"type": "participant.joined",
"timestamp": "2026-06-12T10:02:14Z",
"delivery_id": "e1f3a5c7-2b4d-4068-9a1c-3e5f7b9d0e44",
"organization_id": "1d7e5c9a-3b2f-4e80-9c1a-6d8f0b2e4a55",
"data": {
"meeting_id": "7a2b4c6d-8e0f-4a12-9b34-5c6d7e8f9a01",
"participant_id": "5c7e9a1b-3d4f-4602-8a1c-9b0d2e4f6a88",
"user_id": "3e5f7a9b-1c2d-4e60-8f01-2a3b4c5d6e70",
"name": "Alice Janssen",
"role": "participant",
"timestamp": "2026-06-12T10:02:14Z"
}
}data field | Type | Notes |
|---|---|---|
meeting_id | UUID string | The meeting the participant belongs to. |
participant_id | UUID string | The participant record for this person in this meeting. |
user_id | UUID string or null | The Quantum Club user. null for an unauthenticated guest. |
name | string | The participant's name; falls back to "Unknown" when no name is on record. |
role | string | The participant's role in the meeting. |
timestamp | ISO 8601 string | The join time for participant.joined, the leave time for participant.left. |
The data.timestamp is the moment the participant joined or left; the envelope timestamp is when the event was produced. They are usually the same to the second but are computed independently.
recording.ready
Emitted once per recording when it finishes processing and becomes available.
{
"id": "8c0e2a4f-6b1d-4e30-9a52-1c3e5f7b9d00",
"type": "recording.ready",
"timestamp": "2026-06-12T11:14:09Z",
"delivery_id": "a3c5e7f9-0b2d-4e41-8a63-2d4f6b8e0a11",
"organization_id": "1d7e5c9a-3b2f-4e80-9c1a-6d8f0b2e4a55",
"data": {
"meeting_id": "7a2b4c6d-8e0f-4a12-9b34-5c6d7e8f9a01",
"recording_id": "6d8f0b2e-4a5c-4e71-8b93-0c1d2e3f4a55",
"duration_seconds": 3120,
"size_bytes": 482344960
}
}data field | Type | Notes |
|---|---|---|
meeting_id | UUID string | The meeting the recording belongs to. |
recording_id | UUID string | The recording that is now ready. |
duration_seconds | integer | Recording length in seconds. |
size_bytes | integer | Recording file size in bytes. |
The event signals availability and carries the recording's identifiers; it does not embed a download link. Fetch the media itself through the meetings surface using recording_id, where access stays subject to the recording's consent and permission checks.
transcript.ready
Emitted once per transcript when it finishes processing.
{
"id": "4a6c8e0f-2b3d-4e51-9a74-3c5e7f9b1d22",
"type": "transcript.ready",
"timestamp": "2026-06-12T11:16:42Z",
"delivery_id": "b5d7f9a1-3c4e-4062-8b85-4e6f8a0c2d33",
"organization_id": "1d7e5c9a-3b2f-4e80-9c1a-6d8f0b2e4a55",
"data": {
"meeting_id": "7a2b4c6d-8e0f-4a12-9b34-5c6d7e8f9a01",
"transcript_id": "9b1d3f5a-7c8e-4092-8a16-5d7f9b1c3e44",
"language": "en"
}
}data field | Type | Notes |
|---|---|---|
meeting_id | UUID string | The meeting the transcript belongs to. |
transcript_id | UUID string | The transcript that is now ready. |
language | string | The transcript's primary language code (for example en). |
Like recording.ready, this event carries identifiers rather than the transcript content. Retrieve the transcript through the meetings surface using transcript_id.
Recordings and transcripts exist only for meetings where participants gave recording consent — see GDPR and data rights. The recording_id and transcript_id reference personal data; treat them, and anything you fetch with them, as sensitive and keep them out of general application logs.
Consumption notes
- Delivery is at-least-once and unordered — deduplicate on
delivery_idand do not infer sequence from arrival order. A retriedparticipant.joinedcan arrive after the matchingparticipant.left. - Use the envelope
timestamp(and the participant payload's owntimestamp) for ordering decisions. - New event types and new
datafields may be added over time as additive, non-breaking changes — see API stability. Ignore unrecognizedtypevalues and unknown fields gracefully rather than failing the delivery; your endpoint only receives types it subscribed to, but a tolerant parser costs nothing.
There are no job.*, candidate.*, or offer.* webhook events today. Integrations that need hiring-pipeline data should not poll for event names that this page does not list.
Your consumer handles all seven meeting-domain events, deduplicates on delivery_id, and degrades gracefully on unknown types.
Related
Webhooks
Build a receiver that verifies signatures, acknowledges fast, and survives retries — everything delivery expects from your endpoint, with working code.
API stability
How the public API changes and what stays stable — the published OpenAPI contract is the source of truth, and additive evolution is the default.

