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.

EventLabelFiresDefault
`lead.created`Lead capturedFires when a viewer submits the gate form.On
`lead.updated`Lead updatedFires when a lead is changed through the API.Off
`goal.reached`Goal reachedFires when a viewer hits a goal step in a route — the conversion signal, with lead context when known.On
`booking.created`Call bookedFires when a viewer books a call through the scheduler.On
`booking.canceled`Call canceledFires when a booked call is canceled — by the invitee or by you.On
`outcome.recorded`Outcome recordedFires 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 abandonedFires 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 readyFires when a new or replaced video finishes processing and can play.On
`video.failed`Video failedFires when a video cannot be processed.On
`video.completed`Video completedFires when a viewer reaches 95% watch depth.On
`gate.completed`Gate passedFires when the gate form is submitted with valid data.Off
`choice_point.selected`Choice selectedFires every time a viewer picks a branch in a route.Off
`video.started`Video startedFires on every play. High volume on busy videos.Off
`audit.logged`Audit eventFires 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": {
    "…": "…"
  }
}
HeaderValue
Content-Typeapplication/json
User-AgentStreamAgent-Webhook/1.0
X-StreamAgent-EventThe event name, for routing before you parse the body.
X-StreamAgent-Idempotency-KeyStable across retries of the same delivery. Store it and ignore repeats.
X-StreamAgent-Signaturet=<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);
}
Node
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)
Python

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.

AttemptAfter
230 seconds
35 minutes
430 minutes
52 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
  }
}
A test delivery

`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.

FieldTypeNotes
idstringLead id. Stable across every later event about this lead.
emailstringLowercased.
first_name / last_name / namestring | nullAs submitted or inferred.
phonestring | null
scoreintegerThe intent score at capture time.
watch_depth_pctintegerFurthest depth reached before submitting.
video_idstring | nullThe video the gate was on.
landing_page_idstring | nullSet when the lead came through a landing page.
source / campaign / utm_*string | nullAttribution as captured.
branch_pathstring | nullThe choices taken in a route before capture.
responsesobject | nullAnswers to in-video questions.
geo_city / geo_region / geo_postal_code / geo_countrystring | nullFrom the request, when present.
created_atISO 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"
  }
}
A lead.created delivery

`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.

FieldTypeNotes
idstringLead id.
emailstringLowercased.
first_name / last_name / namestring | null
phonestring | null
statusstring
scoreintegerUnchanged by API writes.
tagsstring[]
notesstring | null
custom_fieldsobject
sourcestring | null
updated_fieldsstring[]The columns this write changed.
viastringapi.
created_at / updated_at / occurred_atISO 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"
  }
}
A lead.updated delivery

`goal.reached` · Goal reached

Once per viewer per goal step, when the route reaches a step marked as a goal.

FieldTypeNotes
goal_typestringlead, registration, booking, contact, purchase or custom.
goal_labelstring | nullThe label set on the goal step.
value_centsinteger | nullThe value authored on the goal, when any.
route_id / route_slugstringThe route that produced it.
node_idstringThe goal step.
session_idstring | nullThe viewing session.
leadobject | 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
    }
  }
}
A goal.reached delivery

`booking.created` · Call booked

Once per confirmed booking, after the confirmation email is queued.

FieldTypeNotes
booking_idstring
event_namestring | nullThe event type booked.
start_atISO 8601 | nullStart time in UTC.
invitee_timezonestring | null
invitee_name / invitee_email / invitee_phonestring | null
lead_idstring | nullThe lead the booking resolved to.
join_urlstring | nullMeeting link when one exists.
statusstring | nullconfirmed.
created_atISO 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"
  }
}
A booking.created delivery

`booking.canceled` · Call canceled

Once per cancellation, by either side.

FieldTypeNotes
booking_idstring
start_atISO 8601 | null
invitee_name / invitee_email / invitee_phonestring | null
lead_idstring | null
statusstringcanceled.
canceled_bystring | nullhost 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"
  }
}
A booking.canceled delivery

`outcome.recorded` · Outcome recorded

Once per recorded outcome: a purchase from a connected revenue source or a valued goal step.

FieldTypeNotes
outcome_idstring
kindstringpurchase, booking or custom.
sourcestringWhere it was recorded from.
statusstring
value_cents / currencyinteger | null, string | null
labelstring | null
occurred_atISO 8601
lead_id / video_id / route_idstring | nullThe lead and the path that produced it.
branch_pathstring | null
external_idstring | nullThe id in the source system.
partner_id / partner_commission_centsstring | null, integer | nullThe 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
  }
}
A outcome.recorded delivery

`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.

FieldTypeNotes
lead_idstringThe viewer who opened checkout.
emailstring | nullLowercased, when the lead has one.
offerobject{ label, amount_cents, currency } — what they were about to buy.
element_id / route_id / node_id / video_idstring | nullThe offer and the step it sits on.
at_secondsinteger | nullSeconds into the video when checkout opened.
opened_at / expired_atISO 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"
  }
}
A checkout.abandoned delivery

`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.

FieldTypeNotes
idstringVideo id, the same one create_video_* returned.
titlestring
statusstringready or error.
ingest_sourcestringupload, url, youtube, vimeo, wistia, loom, recorder, duplicate, or the AI tool that generated it (higgsfield, heygen, runway): how the video arrived.
source_urlstring | nullThe URL it was fetched from, for URL ingests.
duration_secondsnumber | nullSet when ready.
file_size_bytesinteger | null
resolution / aspect_ratiostring | nullSet when ready.
tagsstring[]
folder_idstring | null
errorobject | nullOn failure: code, title, detail and action, the same words the library shows.
created_at / occurred_atISO 8601When 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"
  }
}
A video.ready delivery

`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.

FieldTypeNotes
idstringVideo id, the same one create_video_* returned.
titlestring
statusstringready or error.
ingest_sourcestringupload, url, youtube, vimeo, wistia, loom, recorder, duplicate, or the AI tool that generated it (higgsfield, heygen, runway): how the video arrived.
source_urlstring | nullThe URL it was fetched from, for URL ingests.
duration_secondsnumber | nullSet when ready.
file_size_bytesinteger | null
resolution / aspect_ratiostring | nullSet when ready.
tagsstring[]
folder_idstring | null
errorobject | nullOn failure: code, title, detail and action, the same words the library shows.
created_at / occurred_atISO 8601When 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"
  }
}
A video.failed delivery

`video.completed` · Video completed

Once per session when playback passes 95%.

FieldTypeNotes
idstring | nullThe recorded event id.
video_idstring
workspace_idstring
event_typestringThe player event name.
internal_namestringThe signal name the event maps from.
session_idstring | null
visitor_fingerprintstring | nullCross-session viewer identity, when known.
timestampISO 8601
watch_depth_pctinteger | null
branch_labelstring | nullFor choice events, the branch picked.
fbp / fbc / gclid / ttclid / ttp / ga_client_idstring | nullClick 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
  }
}
A video.completed delivery

`gate.completed` · Gate passed

Once per session when a gate is passed. Pairs with a lead.created when the submission created a new lead.

FieldTypeNotes
idstring | nullThe recorded event id.
video_idstring
workspace_idstring
event_typestringThe player event name.
internal_namestringThe signal name the event maps from.
session_idstring | null
visitor_fingerprintstring | nullCross-session viewer identity, when known.
timestampISO 8601
watch_depth_pctinteger | null
branch_labelstring | nullFor choice events, the branch picked.
fbp / fbc / gclid / ttclid / ttp / ga_client_idstring | nullClick 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
  }
}
A gate.completed delivery

`choice_point.selected` · Choice selected

Every branch pick, so several per session in a branching route.

FieldTypeNotes
idstring | nullThe recorded event id.
video_idstring
workspace_idstring
event_typestringThe player event name.
internal_namestringThe signal name the event maps from.
session_idstring | null
visitor_fingerprintstring | nullCross-session viewer identity, when known.
timestampISO 8601
watch_depth_pctinteger | null
branch_labelstring | nullFor choice events, the branch picked.
fbp / fbc / gclid / ttclid / ttp / ga_client_idstring | nullClick 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
  }
}
A choice_point.selected delivery

`video.started` · Video started

Every play, including replays.

FieldTypeNotes
idstring | nullThe recorded event id.
video_idstring
workspace_idstring
event_typestringThe player event name.
internal_namestringThe signal name the event maps from.
session_idstring | null
visitor_fingerprintstring | nullCross-session viewer identity, when known.
timestampISO 8601
watch_depth_pctinteger | null
branch_labelstring | nullFor choice events, the branch picked.
fbp / fbc / gclid / ttclid / ttp / ga_client_idstring | nullClick 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
  }
}
A video.started delivery

`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.

FieldTypeNotes
idstringAudit event id.
actionstringA key such as team.invite, domain.connect, leads.assign.
labelstringThe human reading of the action.
actor_user_idstring | nullWho did it; null for a system actor.
targetobject{ type, id, label } of the thing acted on, when there is one.
metadataobjectDetails specific to the action (the new role, the client, the count).
created_atISO 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"
  }
}
A audit.logged delivery