Developer docs
Webhooks
Signed POSTs to a URL you control whenever something happens in your workspace: a lead is captured or changed by API, a goal is reached, a call is booked or canceled, an outcome is recorded, a video finishes processing or fails, or a viewer starts, completes, passes a gate or picks a branch in a video. Available on every plan.
Create an endpoint
On the Integrations page, scroll to Webhooks and choose Add a webhook. Give it a name and a public URL, then pick the events it should receive. Local and private network addresses are rejected when the endpoint is saved. The same can be done by code with an Admin key: see Provisioning on the REST API page.
The endpoint's signing secret is shown once, when it is created. Keep it; it never comes back. If it is lost, delete the endpoint and add it again.
Every endpoint has Send test event, a pause toggle, and a delivery history filtered by success, failed, retrying and pending. A paused endpoint receives nothing until resumed.
One endpoint for every client
An Enterprise agency can give an endpoint on its own workspace the Every client workspace option (or include_clients: true by API). It then also receives every subscribed event fired by each client workspace under the agency, signed with the same secret, and the workspace block in the envelope says which client it came from. Endpoints inside a client workspace keep working as before; a client event reaches both.
Events
14 events can be subscribed to, in two groups. Recommended events fire once per meaningful moment; the high-volume group fires per play or per choice and is off by default.
| Event | Label | Fires | Default |
|---|---|---|---|
| `lead.created` | Lead captured | Fires when a viewer submits the gate form. | On |
| `lead.updated` | Lead updated | Fires when a lead is changed through the API. | Off |
| `goal.reached` | Goal reached | Fires when a viewer hits a goal step in a route — the conversion signal, with lead context when known. | On |
| `booking.created` | Call booked | Fires when a viewer books a call through the scheduler. | On |
| `booking.canceled` | Call canceled | Fires when a booked call is canceled — by the invitee or by you. | On |
| `outcome.recorded` | Outcome recorded | Fires when a purchase, booking, or custom conversion lands in the outcomes ledger — with value, lead, and the path that produced it. | On |
| `checkout.abandoned` | Checkout abandoned | Fires when a viewer opens an offer's checkout inside the video and leaves without paying — the lead, the offer, and the moment in the video. | On |
| `video.ready` | Video ready | Fires when a new or replaced video finishes processing and can play. | On |
| `video.failed` | Video failed | Fires when a video cannot be processed. | On |
| `video.completed` | Video completed | Fires when a viewer reaches 95% watch depth. | On |
| `gate.completed` | Gate passed | Fires when the gate form is submitted with valid data. | Off |
| `choice_point.selected` | Choice selected | Fires every time a viewer picks a branch in a route. | Off |
| `video.started` | Video started | Fires on every play. High volume on busy videos. | Off |
| `audit.logged` | Audit event | Fires for every action a person takes in the workspace. | Off |
An endpoint subscribed to * receives every event, including ones added later.
The request
Each delivery is one POST with a JSON body of four keys: event, the ISO timestamp it fired, workspace (the id, name and handle of the workspace the event belongs to), and data, the event payload documented below. The test event carries no workspace.
{
"event": "lead.created",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"…": "…"
}
}| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | StreamAgent-Webhook/1.0 |
X-StreamAgent-Event | The event name, for routing before you parse the body. |
X-StreamAgent-Idempotency-Key | Stable across retries of the same delivery. Store it and ignore repeats. |
X-StreamAgent-Signature | t=<unix-ms>,v1=<hex HMAC-SHA256 of "<unix-ms>.<raw body>"> |
Respond with any 2xx within 10 seconds. Do the work after you respond; a slow handler is retried as a timeout. Response bodies are kept for the delivery log, truncated to 4,096 bytes.
Verify the signature
Recompute the HMAC over <timestamp>.<raw body> with your endpoint secret and compare it to v1 in constant time. Use the raw request bytes, not a re-serialized object. Reject anything older than 5 minutes to shut out replays.
const crypto = require('crypto');
// signatureHeader is the X-StreamAgent-Signature header: "t=<ms>,v1=<hex>"
function verifyStreamAgentSignature(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
const ts = parseInt(parts.t, 10);
const sig = parts.v1;
if (!ts || !sig) return false;
// Reject replays older than 5 minutes.
if (Math.abs(Date.now() - ts) > 300000) return false;
const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
const a = Buffer.from(sig, 'hex');
const b = Buffer.from(expected, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hmac
import hashlib
import time
# signature_header is the X-StreamAgent-Signature header: "t=<ms>,v1=<hex>"
def verify_streamagent_signature(raw_body: str, signature_header: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in signature_header.split(','))
ts = int(parts.get('t', 0))
sig = parts.get('v1', '')
if not ts or not sig:
return False
# Reject replays older than 5 minutes.
if abs(int(time.time() * 1000) - ts) > 300000:
return False
expected = hmac.new(secret.encode('utf-8'), f'{ts}.{raw_body}'.encode('utf-8'), hashlib.sha256).hexdigest()
return hmac.compare_digest(sig, expected)Retries and idempotency
A delivery is retried when your endpoint times out, fails to connect, returns a 5xx, or returns 408 or 429. Any other 4xx is treated as your decision and is not retried.
| Attempt | After |
|---|---|
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
5 attempts in total, then the delivery is marked failed and stays in the history. Every attempt of one delivery carries the same X-StreamAgent-Idempotency-Key; a handler that has already processed it should return 2xx without acting again.
The test event
When you press Send test event. The body carries _test: true and a synthetic lead.
{
"event": "webhook.test",
"timestamp": "2026-09-07T14:03:11.412Z",
"data": {
"_test": true,
"email": "test@streamagent.io",
"name": "Test Lead",
"first_name": "Test",
"last_name": "Lead",
"lead_score": 0,
"source": "test_event",
"campaign": null,
"watch_depth_pct": 0,
"video_watched": null
}
}`lead.created` · Lead captured
Once per new lead, the moment the gate form (or a booking, a comment with contact details, or an API create) creates the lead record. Updates to an existing lead fire lead.updated instead.
| Field | Type | Notes |
|---|---|---|
| id | string | Lead id. Stable across every later event about this lead. |
| string | Lowercased. | |
| first_name / last_name / name | string | null | As submitted or inferred. |
| phone | string | null | |
| score | integer | The intent score at capture time. |
| watch_depth_pct | integer | Furthest depth reached before submitting. |
| video_id | string | null | The video the gate was on. |
| landing_page_id | string | null | Set when the lead came through a landing page. |
| source / campaign / utm_* | string | null | Attribution as captured. |
| branch_path | string | null | The choices taken in a route before capture. |
| responses | object | null | Answers to in-video questions. |
| geo_city / geo_region / geo_postal_code / geo_country | string | null | From the request, when present. |
| created_at | ISO 8601 |
{
"event": "lead.created",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"id": "9f1c2e6a-7d4b-4c1e-9a2f-0b3d5e7f9a1c",
"email": "maria@example.com",
"first_name": "Maria",
"last_name": "Lopez",
"name": "Maria Lopez",
"phone": null,
"score": 42,
"watch_depth_pct": 75,
"video_id": "4b8e1d3c-2a6f-4e9b-8c1d-7f2a9e4b6c3d",
"landing_page_id": null,
"source": "gate",
"campaign": null,
"utm_source": "meta",
"utm_medium": "paid",
"utm_campaign": "spring-offer",
"branch_path": "pricing > annual",
"responses": {
"budget": "over 5k"
},
"geo_city": "Austin",
"geo_region": "TX",
"geo_postal_code": "78701",
"geo_country": "US",
"created_at": "2026-09-07T14:03:11.412Z"
}
}`lead.updated` · Lead updated
Once per API write to an existing lead: an upsert that matched by email, or an update of status, tags, notes or contact details. Edits made by hand in the dashboard do not fire it.
| Field | Type | Notes |
|---|---|---|
| id | string | Lead id. |
| string | Lowercased. | |
| first_name / last_name / name | string | null | |
| phone | string | null | |
| status | string | |
| score | integer | Unchanged by API writes. |
| tags | string[] | |
| notes | string | null | |
| custom_fields | object | |
| source | string | null | |
| updated_fields | string[] | The columns this write changed. |
| via | string | api. |
| created_at / updated_at / occurred_at | ISO 8601 |
{
"event": "lead.updated",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"id": "9f1c2e6a-7d4b-4c1e-9a2f-0b3d5e7f9a1c",
"email": "maria@example.com",
"first_name": "Maria",
"last_name": "Lopez",
"name": "Maria Lopez",
"phone": "+1 512 555 0134",
"status": "qualified",
"score": 42,
"tags": [
"webinar",
"enterprise"
],
"notes": "[2026-09-07] Booked a demo from the CRM.",
"custom_fields": {
"crm_id": "0031x000"
},
"source": "crm",
"updated_fields": [
"notes",
"phone",
"status"
],
"via": "api",
"created_at": "2026-09-01T09:12:00.000Z",
"updated_at": "2026-09-07T14:03:11.412Z",
"occurred_at": "2026-09-07T14:03:11.412Z"
}
}`goal.reached` · Goal reached
Once per viewer per goal step, when the route reaches a step marked as a goal.
| Field | Type | Notes |
|---|---|---|
| goal_type | string | lead, registration, booking, contact, purchase or custom. |
| goal_label | string | null | The label set on the goal step. |
| value_cents | integer | null | The value authored on the goal, when any. |
| route_id / route_slug | string | The route that produced it. |
| node_id | string | The goal step. |
| session_id | string | null | The viewing session. |
| lead | object | null | { id, email, score, watch_depth_pct } when the viewer is a known lead. |
{
"event": "goal.reached",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"goal_type": "purchase",
"goal_label": "Annual plan",
"value_cents": 49900,
"route_id": "c2d4f6a8-1b3e-4d5f-a7c9-e1f3a5b7c9d1",
"route_slug": "pricing-walkthrough",
"node_id": "n_goal_1",
"session_id": "s_7c1e",
"lead": {
"id": "9f1c2e6a-7d4b-4c1e-9a2f-0b3d5e7f9a1c",
"email": "maria@example.com",
"score": 42,
"watch_depth_pct": 75
}
}
}`booking.created` · Call booked
Once per confirmed booking, after the confirmation email is queued.
| Field | Type | Notes |
|---|---|---|
| booking_id | string | |
| event_name | string | null | The event type booked. |
| start_at | ISO 8601 | null | Start time in UTC. |
| invitee_timezone | string | null | |
| invitee_name / invitee_email / invitee_phone | string | null | |
| lead_id | string | null | The lead the booking resolved to. |
| join_url | string | null | Meeting link when one exists. |
| status | string | null | confirmed. |
| created_at | ISO 8601 | null |
{
"event": "booking.created",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"booking_id": "b_2f9d",
"event_name": "Strategy call",
"start_at": "2026-09-09T21:00:00.000Z",
"invitee_timezone": "America/Los_Angeles",
"invitee_name": "Maria Lopez",
"invitee_email": "maria@example.com",
"invitee_phone": null,
"lead_id": "9f1c2e6a-7d4b-4c1e-9a2f-0b3d5e7f9a1c",
"join_url": "https://meet.example.com/abc",
"status": "confirmed",
"created_at": "2026-09-07T14:03:11.412Z"
}
}`booking.canceled` · Call canceled
Once per cancellation, by either side.
| Field | Type | Notes |
|---|---|---|
| booking_id | string | |
| start_at | ISO 8601 | null | |
| invitee_name / invitee_email / invitee_phone | string | null | |
| lead_id | string | null | |
| status | string | canceled. |
| canceled_by | string | null | host or invitee. |
{
"event": "booking.canceled",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"booking_id": "b_2f9d",
"start_at": "2026-09-09T21:00:00.000Z",
"invitee_name": "Maria Lopez",
"invitee_email": "maria@example.com",
"invitee_phone": null,
"lead_id": "9f1c2e6a-7d4b-4c1e-9a2f-0b3d5e7f9a1c",
"status": "canceled",
"canceled_by": "invitee"
}
}`outcome.recorded` · Outcome recorded
Once per recorded outcome: a purchase from a connected revenue source or a valued goal step.
| Field | Type | Notes |
|---|---|---|
| outcome_id | string | |
| kind | string | purchase, booking or custom. |
| source | string | Where it was recorded from. |
| status | string | |
| value_cents / currency | integer | null, string | null | |
| label | string | null | |
| occurred_at | ISO 8601 | |
| lead_id / video_id / route_id | string | null | The lead and the path that produced it. |
| branch_path | string | null | |
| external_id | string | null | The id in the source system. |
| partner_id / partner_commission_cents | string | null, integer | null | The partner credited with the sale (Partner Routes) and the commission snapshotted at record time. |
{
"event": "outcome.recorded",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"outcome_id": "o_51ab",
"kind": "purchase",
"source": "stripe",
"status": "confirmed",
"value_cents": 49900,
"currency": "usd",
"label": "Annual plan",
"occurred_at": "2026-09-07T14:03:11.412Z",
"lead_id": "9f1c2e6a-7d4b-4c1e-9a2f-0b3d5e7f9a1c",
"video_id": "4b8e1d3c-2a6f-4e9b-8c1d-7f2a9e4b6c3d",
"route_id": "c2d4f6a8-1b3e-4d5f-a7c9-e1f3a5b7c9d1",
"branch_path": "pricing > annual",
"external_id": "pi_3Nz…",
"partner_id": null,
"partner_commission_cents": null
}
}`checkout.abandoned` · Checkout abandoned
Once per abandoned checkout, when the open checkout expires unpaid (about 30 minutes after it opened). Never fires for a checkout that was paid.
| Field | Type | Notes |
|---|---|---|
| lead_id | string | The viewer who opened checkout. |
| string | null | Lowercased, when the lead has one. | |
| offer | object | { label, amount_cents, currency } — what they were about to buy. |
| element_id / route_id / node_id / video_id | string | null | The offer and the step it sits on. |
| at_seconds | integer | null | Seconds into the video when checkout opened. |
| opened_at / expired_at | ISO 8601 | null |
{
"event": "checkout.abandoned",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"lead_id": "9f1c2e6a-7d4b-4c1e-9a2f-0b3d5e7f9a1c",
"email": "maria@example.com",
"offer": {
"label": "The Launch Workshop",
"amount_cents": 49700,
"currency": "USD"
},
"element_id": "el_7f2c",
"route_id": "r_9d1e",
"node_id": "n_2b8a",
"video_id": "4b8e1d3c-2a6f-4e9b-8c1d-7f2a9e4b6c3d",
"at_seconds": 2480,
"opened_at": "2026-09-07T14:03:11.412Z",
"expired_at": "2026-09-07T14:03:11.412Z"
}
}`video.ready` · Video ready
Once per video that finishes processing: an upload, a URL ingest, a Studio take, a duplicate, or a replacement. The natural follow-up to creating a video by API.
| Field | Type | Notes |
|---|---|---|
| id | string | Video id, the same one create_video_* returned. |
| title | string | |
| status | string | ready or error. |
| ingest_source | string | upload, url, youtube, vimeo, wistia, loom, recorder, duplicate, or the AI tool that generated it (higgsfield, heygen, runway): how the video arrived. |
| source_url | string | null | The URL it was fetched from, for URL ingests. |
| duration_seconds | number | null | Set when ready. |
| file_size_bytes | integer | null | |
| resolution / aspect_ratio | string | null | Set when ready. |
| tags | string[] | |
| folder_id | string | null | |
| error | object | null | On failure: code, title, detail and action, the same words the library shows. |
| created_at / occurred_at | ISO 8601 | When the video was created, and when this event fired. |
{
"event": "video.ready",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"id": "4b8e1d3c-2a6f-4e9b-8c1d-7f2a9e4b6c3d",
"title": "Spring offer walkthrough",
"status": "ready",
"ingest_source": "url",
"source_url": "https://cdn.example.com/spring-offer.mp4",
"duration_seconds": 184.2,
"file_size_bytes": 58204113,
"resolution": "1080p",
"aspect_ratio": "16:9",
"tags": [
"spring",
"offer"
],
"folder_id": null,
"error": null,
"created_at": "2026-09-07T13:58:40.000Z",
"occurred_at": "2026-09-07T14:03:11.412Z"
}
}`video.failed` · Video failed
Once per failed ingest: the file could not be fetched, was not a video, or the upload never completed. error says why and what to do.
| Field | Type | Notes |
|---|---|---|
| id | string | Video id, the same one create_video_* returned. |
| title | string | |
| status | string | ready or error. |
| ingest_source | string | upload, url, youtube, vimeo, wistia, loom, recorder, duplicate, or the AI tool that generated it (higgsfield, heygen, runway): how the video arrived. |
| source_url | string | null | The URL it was fetched from, for URL ingests. |
| duration_seconds | number | null | Set when ready. |
| file_size_bytes | integer | null | |
| resolution / aspect_ratio | string | null | Set when ready. |
| tags | string[] | |
| folder_id | string | null | |
| error | object | null | On failure: code, title, detail and action, the same words the library shows. |
| created_at / occurred_at | ISO 8601 | When the video was created, and when this event fired. |
{
"event": "video.failed",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"id": "4b8e1d3c-2a6f-4e9b-8c1d-7f2a9e4b6c3d",
"title": "Spring offer walkthrough",
"status": "error",
"ingest_source": "url",
"source_url": "https://cdn.example.com/spring-offer.mp4",
"duration_seconds": null,
"file_size_bytes": 58204113,
"resolution": null,
"aspect_ratio": null,
"tags": [
"spring",
"offer"
],
"folder_id": null,
"error": {
"code": "invalid_url",
"title": "The link did not point at a video",
"detail": "The address returned a web page rather than a media file.",
"action": "Use a direct download link to the file and try again."
},
"created_at": "2026-09-07T13:58:40.000Z",
"occurred_at": "2026-09-07T14:03:11.412Z"
}
}`video.completed` · Video completed
Once per session when playback passes 95%.
| Field | Type | Notes |
|---|---|---|
| id | string | null | The recorded event id. |
| video_id | string | |
| workspace_id | string | |
| event_type | string | The player event name. |
| internal_name | string | The signal name the event maps from. |
| session_id | string | null | |
| visitor_fingerprint | string | null | Cross-session viewer identity, when known. |
| timestamp | ISO 8601 | |
| watch_depth_pct | integer | null | |
| branch_label | string | null | For choice events, the branch picked. |
| fbp / fbc / gclid / ttclid / ttp / ga_client_id | string | null | Click ids and cookies when the player received them. |
{
"event": "video.completed",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"id": "e_8a2c",
"video_id": "4b8e1d3c-2a6f-4e9b-8c1d-7f2a9e4b6c3d",
"workspace_id": "w_1",
"event_type": "watch_95",
"internal_name": "VideoWatch95",
"session_id": "s_7c1e",
"visitor_fingerprint": "fp_3e9a",
"timestamp": "2026-09-07T14:03:11.412Z",
"watch_depth_pct": 95,
"branch_label": null,
"fbp": null,
"fbc": null,
"gclid": "Cj0KCQ…",
"ttclid": null,
"ttp": null,
"ga_client_id": null
}
}`gate.completed` · Gate passed
Once per session when a gate is passed. Pairs with a lead.created when the submission created a new lead.
| Field | Type | Notes |
|---|---|---|
| id | string | null | The recorded event id. |
| video_id | string | |
| workspace_id | string | |
| event_type | string | The player event name. |
| internal_name | string | The signal name the event maps from. |
| session_id | string | null | |
| visitor_fingerprint | string | null | Cross-session viewer identity, when known. |
| timestamp | ISO 8601 | |
| watch_depth_pct | integer | null | |
| branch_label | string | null | For choice events, the branch picked. |
| fbp / fbc / gclid / ttclid / ttp / ga_client_id | string | null | Click ids and cookies when the player received them. |
{
"event": "gate.completed",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"id": "e_8a2c",
"video_id": "4b8e1d3c-2a6f-4e9b-8c1d-7f2a9e4b6c3d",
"workspace_id": "w_1",
"event_type": "gate_passed",
"internal_name": "GatePassed",
"session_id": "s_7c1e",
"visitor_fingerprint": "fp_3e9a",
"timestamp": "2026-09-07T14:03:11.412Z",
"watch_depth_pct": 50,
"branch_label": null,
"fbp": null,
"fbc": null,
"gclid": "Cj0KCQ…",
"ttclid": null,
"ttp": null,
"ga_client_id": null
}
}`choice_point.selected` · Choice selected
Every branch pick, so several per session in a branching route.
| Field | Type | Notes |
|---|---|---|
| id | string | null | The recorded event id. |
| video_id | string | |
| workspace_id | string | |
| event_type | string | The player event name. |
| internal_name | string | The signal name the event maps from. |
| session_id | string | null | |
| visitor_fingerprint | string | null | Cross-session viewer identity, when known. |
| timestamp | ISO 8601 | |
| watch_depth_pct | integer | null | |
| branch_label | string | null | For choice events, the branch picked. |
| fbp / fbc / gclid / ttclid / ttp / ga_client_id | string | null | Click ids and cookies when the player received them. |
{
"event": "choice_point.selected",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"id": "e_8a2c",
"video_id": "4b8e1d3c-2a6f-4e9b-8c1d-7f2a9e4b6c3d",
"workspace_id": "w_1",
"event_type": "branch_selected",
"internal_name": "BranchSelected",
"session_id": "s_7c1e",
"visitor_fingerprint": "fp_3e9a",
"timestamp": "2026-09-07T14:03:11.412Z",
"watch_depth_pct": null,
"branch_label": "Annual",
"fbp": null,
"fbc": null,
"gclid": "Cj0KCQ…",
"ttclid": null,
"ttp": null,
"ga_client_id": null
}
}`video.started` · Video started
Every play, including replays.
| Field | Type | Notes |
|---|---|---|
| id | string | null | The recorded event id. |
| video_id | string | |
| workspace_id | string | |
| event_type | string | The player event name. |
| internal_name | string | The signal name the event maps from. |
| session_id | string | null | |
| visitor_fingerprint | string | null | Cross-session viewer identity, when known. |
| timestamp | ISO 8601 | |
| watch_depth_pct | integer | null | |
| branch_label | string | null | For choice events, the branch picked. |
| fbp / fbc / gclid / ttclid / ttp / ga_client_id | string | null | Click ids and cookies when the player received them. |
{
"event": "video.started",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"id": "e_8a2c",
"video_id": "4b8e1d3c-2a6f-4e9b-8c1d-7f2a9e4b6c3d",
"workspace_id": "w_1",
"event_type": "play",
"internal_name": "VideoStart",
"session_id": "s_7c1e",
"visitor_fingerprint": "fp_3e9a",
"timestamp": "2026-09-07T14:03:11.412Z",
"watch_depth_pct": 0,
"branch_label": null,
"fbp": null,
"fbc": null,
"gclid": "Cj0KCQ…",
"ttclid": null,
"ttp": null,
"ga_client_id": null
}
}`audit.logged` · Audit event
Once per human action the audit log records: team changes, domains, sending domains, playbooks, lead routing and assignment, exports, deletions, settings, and suggestion decisions. Agent, connector and system changes are not audit events; they are in the Changes ledger.
| Field | Type | Notes |
|---|---|---|
| id | string | Audit event id. |
| action | string | A key such as team.invite, domain.connect, leads.assign. |
| label | string | The human reading of the action. |
| actor_user_id | string | null | Who did it; null for a system actor. |
| target | object | { type, id, label } of the thing acted on, when there is one. |
| metadata | object | Details specific to the action (the new role, the client, the count). |
| created_at | ISO 8601 |
{
"event": "audit.logged",
"timestamp": "2026-09-07T14:03:11.412Z",
"workspace": {
"id": "w_1",
"name": "Acme Realty",
"handle": "acme-realty"
},
"data": {
"id": "7c1d9e3a-5b2f-4a8c-9d1e-2f3a4b5c6d7e",
"action": "team.role",
"label": "Changed a role",
"actor_user_id": "2a4c6e8a-1b3d-4f5a-8c9e-0d1f2a3b4c5d",
"target": {
"type": "member",
"id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
"label": "ana@acme.com"
},
"metadata": {
"role": "admin"
},
"created_at": "2026-09-07T14:03:11.412Z"
}
}