Outbound Webhooks
Receive real-time events about videos, telemetry, embedding, and quotas.
Outbound webhooks notify your HTTPS endpoints when something important happens in Moviie. Each delivery is asynchronous, retried automatically, and can also be resent manually.
Delivery, timeouts, and retries
Moviie sends each notification as an HTTP POST and treats a delivery as successful
when your server responds with any 2xx status. The request body is the JSON envelope
described below.
Timeout. Each attempt waits up to 30 seconds for a response. If the connection times out or fails at the network layer (DNS, TLS, reset, etc.), Moviie classifies it as a transient failure and applies the same retry policy as for retryable HTTP responses.
Retryable HTTP responses. Moviie automatically retries when your endpoint returns 408, 429, or any status ≥ 500. Those cases are assumed to be temporary overload or upstream issues.
Non-retryable HTTP responses. Any other status (for example 401, 403, 404, 422) marks the delivery as failed without further automatic attempts. Fix the endpoint or credentials and then use a manual resend from the dashboard when available.
Backoff. Between automatic attempts, Moviie uses a quadratic backoff measured in
minutes, capped at 6 hours between tries. The current attempt index is exposed in the
X-Moviie-Attempt header (1 on the first try, then increments). The platform makes
at most six HTTP attempts per delivery; if all fail, the delivery stays failed until
you intervene.
Manual retries. After automatic retries are exhausted (or after a non-retryable response), you can trigger delivery again from Settings → Webhooks whenever that action is available for the endpoint.
Request format
Every request body is a JSON object with the following top-level fields:
| Field | Type | Description |
|---|---|---|
id | string | Delivery envelope id; equals X-Moviie-Event-Id (evt_ + UUID). |
type | string | Canonical event name; equals X-Moviie-Event. |
created_at | string | ISO 8601 timestamp when the envelope was built. |
organization_id | string | UUID of the Moviie organization that owns the resource. |
data | object | Event-specific payload: see the event reference pages. |
{
"id": "evt_7b9e4f2a-0c1d-4e8f-9a3b-2d6c8e1f4a5b",
"type": "video.upload.started",
"created_at": "2026-05-09T18:30:00.000Z",
"organization_id": "11111111-1111-1111-1111-111111111111",
"data": {
"video": { "id": "00000000-0000-4000-8000-00000000aa01" }
}
}Subscribing to an event in Settings → Webhooks only controls whether deliveries are created; every delivery uses this same envelope. Request headers, authentication, and signature verification are covered in Signing & Verification.
Shared data.video object
Many events include a video object built from the library record. Unless an event's
reference entry says otherwise, data.video contains:
| Field | Type | Presence |
|---|---|---|
id | string (UUID) | Always |
external_id | string | Always |
status | string | Always |
title | string | Included when non-empty |
duration_ms | number | Included when known |
width | number | Included when known |
height | number | Included when known |
collection_id | string (UUID) | Included when set |
Some events send a minimal video containing only id; those are noted explicitly in
the event entry.
Event catalog
Only events enabled for your endpoint are enqueued. Click any type to jump to the
full body example and parameter table.
type | Summary |
|---|---|
collection.created | New collection created. |
collection.deleted | Collection soft-deleted. |
embed.blocked | Embed blocked by referrer policy. |
embed.token.invalid | Telemetry refresh token rejected. |
playback.cta.clicked | CTA clicked during playback. |
playback.error | Player error reported for a session. |
playback.milestone.25 | Session reached ~25% of duration. |
playback.milestone.50 | Session reached ~50% of duration. |
playback.milestone.75 | Session reached ~75% of duration. |
playback.milestone.100 | Session reached ~100% of duration. |
playback.session.ended | Playback session ended (aggregated). |
usage.bandwidth.threshold | Bandwidth hits 80%, 95%, or 100% of plan. |
usage.storage.threshold | Storage hits 80%, 95%, or 100% of plan. |
video.caption.added | Caption track added. |
video.caption.deleted | Caption track removed. |
video.chapter.created | Chapter created. |
video.chapter.deleted | Chapter deleted. |
video.collection.changed | Video moved to another collection. |
video.completion_rate.threshold | Daily completion rate crossed 40% or 50%. |
video.cta.created | CTA created. |
video.cta.deleted | CTA deleted. |
video.cta.updated | CTA updated. |
video.deleted | Video soft-deleted. |
video.encoding.completed | A resolution finished encoding. |
video.encoding.failed | Encoding failed (confirmed). |
video.encoding.progress | Encoding progress update. |
video.encoding.started | Encoding started. |
video.failed | Fatal provider failure (confirmed). |
video.published | Video first became fully playable. |
video.ready | All renditions ready to play. |
video.unpublished | Video is no longer publicly available. |
video.updated | Video metadata updated. |
video.upload.completed | File ingested at provider. |
video.upload.failed | Upload failed (confirmed). |
video.upload.started | Upload slot reserved. |
video.view.milestone | Total views crossed 100, 1 000, or 10 000. |
Event timing notes
Some failure paths are intentionally delayed: transient ingest-provider statuses are
buffered, and only confirmed failures emit video.encoding.failed,
video.upload.failed, or video.failed after the failure confirmer job checks the provider
state: never from a single flaky callback alone.
Telemetry-driven engagement events (playback.*, milestones, thresholds) flush when the
telemetry aggregator processes stored heartbeats. Latency depends on how often aggregation
runs; in typical local development this is around five minutes.