Event types
Events are grouped by domain. Each type is a stable string — pick the ones
your integration cares about when registering the webhook.
Asset creation
Section titled “Asset creation”Fired by the legacy ingest path when an upload is processed.
| Type | When |
|---|---|
asset.creation.succeeded | Asset successfully transcoded and ready for analysis |
asset.creation.failed | Transcode failed (corrupt source, unsupported codec, network error) |
Asset analysis (“clips ready”)
Section titled “Asset analysis (“clips ready”)”This is the event most automations subscribe to. It fires when the analyze → transcribe → suggest → preview pipeline completes for an asset.
| Type | When |
|---|---|
asset.analysis.started | Pipeline accepted, work queued |
asset.analysis.progress | Periodic progress update (high volume — only subscribe if you display progress) |
asset.analysis.succeeded | Clips are ready. GET /v1/clips/:jobId will now return populated clips[] with previews. |
asset.analysis.failed | Pipeline errored. The folder doc has a structured pipelineError; the dashboard surfaces the same message. |
Project render
Section titled “Project render”Fired by the full-quality render pipeline triggered via POST /v1/clips/:clipId/renders.
| Type | When |
|---|---|
project.render.started | Render queued |
project.render.progress | Periodic progress update (high volume) |
project.render.succeeded | Render done. GET /v1/renders/:renderId will return output_url. |
project.render.failed | Render errored |
Social posts
Section titled “Social posts”Fired across the post lifecycle — from the moment AI-generated drafts are created (when a clip’s preview becomes ready) through to the platform publish.
| Type | When |
|---|---|
post.draft.created | A draft post was created for a clip. One event fires per platform — GET /v1/clips/:clipId/posts will now return it. |
post.scheduled | A draft was scheduled for a future publish time via POST /v1/posts/:id/publish with a scheduled_at. |
post.published | The post landed on the platform. published_url is included for direct linking; GET /v1/posts/:id has the same data. |
post.failed | Publish failed permanently. The error field has a human-readable reason. |
Payload schema
Section titled “Payload schema”Every delivery shares the same envelope:
{ "id": "<unique event id, use for dedup>", "type": "<event type>", "created": <unix-ms timestamp>, "data": { ... event-specific, see table below ... }}The data shape per event is intentionally small — webhooks are
notifications, not full responses. Use the V1 GET endpoints to fetch
clips, output URLs, and detailed state. That keeps signed URLs fresh,
the HMAC verification simple, and lets your handler ack in milliseconds.
| Event | data shape |
|---|---|
asset.creation.succeeded | { asset_id, team_id, duration_seconds, job_id } |
asset.creation.failed | { asset_id, team_id, error, job_id } |
asset.analysis.started | { asset_id, team_id, job_id } |
asset.analysis.progress | { asset_id, team_id, progress, job_id } (0-1) |
asset.analysis.succeeded | { asset_id, team_id, job_id } |
asset.analysis.failed | { asset_id, team_id, error, job_id } |
project.render.started | { project_id, team_id } |
project.render.progress | { project_id, team_id, progress } (0-1) |
project.render.succeeded | { project_id, team_id } |
project.render.failed | { project_id, team_id, error } |
post.draft.created | { post_id, clip_id, platform, team_id } |
post.scheduled | { post_id, clip_id, platform, team_id, scheduled_at } |
post.published | { post_id, clip_id, platform, team_id, published_url } |
post.failed | { post_id, clip_id, platform, team_id, error } |
job_id is the job_id returned by POST /v1/clips. Receivers should use it directly with GET /v1/clips/:jobId — no asset_id-to-job_id lookup needed. It’s emitted on every asset.* event for clip jobs started via the public API.
Example payloads
Section titled “Example payloads”asset.analysis.succeeded
Section titled “asset.analysis.succeeded”{ "id": "9f8e7d6c-...", "type": "asset.analysis.succeeded", "created": 1745800000000, "data": { "asset_id": "asset_xyz", "team_id": "team_abc123", "job_id": "fb9a7c3e-..." }}Then call GET /v1/clips/<job_id> to fetch the generated clips. The job_id is the same value you got back from POST /v1/clips.
project.render.succeeded
Section titled “project.render.succeeded”{ "id": "fb9a7c3e-...", "type": "project.render.succeeded", "created": 1745800045000, "data": { "project_id": "9d8e7f6a-...", "team_id": "team_abc123" }}Then call GET /v1/renders/<project_id> to fetch the signed output_url.
asset.analysis.failed
Section titled “asset.analysis.failed”{ "id": "ab12cd34-...", "type": "asset.analysis.failed", "created": 1745800000000, "data": { "asset_id": "asset_xyz", "team_id": "team_abc123", "job_id": "fb9a7c3e-...", "error": "We couldn't find the uploaded video. Try uploading again." }}post.published
Section titled “post.published”{ "id": "1c2b3a4d-...", "type": "post.published", "created": 1745800120000, "data": { "post_id": "9d8e7f6a-...", "clip_id": "fc3eb2a1-...", "platform": "youtube", "team_id": "team_abc123", "published_url": "https://www.youtube.com/watch?v=abc123" }}Call GET /v1/posts/<post_id> for the full post record.
Forwards-compatibility
Section titled “Forwards-compatibility”We only add new fields, never remove or rename. Parse defensively and ignore unknown keys.
Volume guidance
Section titled “Volume guidance”The *.progress events fire every few seconds during long jobs. If you
subscribe to them, make sure your endpoint can absorb the burst — a 30-minute
analysis can produce ~100 progress events. Prefer *.succeeded /
*.failed for state-change-driven flows.