Skip to content

Webhooks

Odeion can send real-time notifications to external services whenever something happens on your server, such as a movie being added, a user starting playback, or a download completing. Webhooks support two delivery modes: standard HTTP POST and Discord-native rich embeds.

  1. Go to Admin > Webhooks.
  2. Click Add Webhook.
  3. Choose a type: HTTP or Discord.
  4. Enter the endpoint URL.
  5. Select which events to subscribe to.
  6. Click Create.

The webhook will begin delivering events immediately.

Standard HTTP POST requests. Each delivery sends a JSON payload to your endpoint with this structure:

{
"event": "media.added",
"timestamp": "2026-03-29T12:00:00Z",
"data": {
"media_type": "movie",
"media_id": 42,
"title": "Inception",
"year": 2010,
"overview": "A thief who steals corporate secrets...",
"poster_path": "/path/to/poster.jpg",
"library_id": 1,
"library_name": "Movies"
}
}

Every request includes these headers:

HeaderDescription
Content-TypeAlways application/json
User-AgentOdeion-Webhooks/1.0
X-Odeion-EventThe event type (e.g. playback.started)
X-Odeion-DeliveryUnique delivery ID for deduplication
X-Odeion-SignatureHMAC-SHA256 signature (only if a secret is configured)

If you set a secret on the webhook, Odeion signs every request body using HMAC-SHA256 and includes the signature in the X-Odeion-Signature header as sha256=<hex>. To verify:

  1. Compute HMAC-SHA256(secret, raw_request_body).
  2. Compare the hex digest against the value after sha256=.

This prevents spoofed deliveries if your endpoint is publicly reachable.

HTTP webhooks support custom headers for authentication with third-party services. For example, you can add an Authorization: Bearer <token> header to authenticate with your API.

Discord webhooks use rich embeds with poster art, color-coded titles, and structured metadata fields. No bot or OAuth setup is required. Just paste a Discord webhook URL.

To get a webhook URL:

  1. Open your Discord server settings.
  2. Go to Integrations > Webhooks.
  3. Click New Webhook, choose a channel, and copy the URL.
  4. Paste the URL into Odeion.

Discord deliveries automatically handle rate limits. If Discord returns a 429 (Too Many Requests), Odeion waits for the specified cooldown period before retrying. Sends to the same Discord webhook URL are serialized to avoid bursts.

Events are grouped into categories. You can subscribe to any combination per webhook.

EventFires when
playback.startedA user begins playing a movie or episode
playback.stoppedA user stops playback
media.watchedA user finishes a movie or episode (reaches 90%+ or is marked complete)

Playback events include the user’s name, media title, series/episode context for TV shows, poster art, and current position/duration.

EventFires when
media.addedA new movie or series is added and matched to metadata
scan.completedA library scan finishes, with counts of added/removed/updated files

The media.added event fires after the automatic metadata matching step, so the payload includes the title, year, overview, poster, and library name. For TV shows, the event fires at the series level (not per-episode).

EventFires when
download.readyA transcoded download is ready for the user to collect
download.failedA transcode/download job failed
EventFires when
task.completedA background processing task finishes successfully
task.failedA background processing task fails

System events include the task type (probe, associate, media_extract, etc.) and error details for failures.

When a delivery fails (network error, non-2xx response), Odeion retries with exponential backoff:

AttemptDelay
1st retry10 seconds
2nd retry30 seconds
3rd retry90 seconds
4th retry~4.5 minutes
5th retry~13.5 minutes

After 5 failed attempts, the delivery is marked as permanently failed. Delivery records are retained for 30 days and then automatically cleaned up.

Every webhook has a detailed delivery log accessible from the admin panel. Click on any webhook to see its recent deliveries with:

  • Event type, status (success/pending/failed), and HTTP response code
  • Duration and attempt count
  • Full request and response headers and bodies
  • Error messages for failed deliveries

You can retry any failed delivery manually from the delivery detail view, and test any webhook with a synthetic test event to verify your endpoint is reachable before subscribing to real events.

Playback Events (playback.started, playback.stopped)

Section titled “Playback Events (playback.started, playback.stopped)”
{
"user_id": 1,
"username": "alice",
"media_type": "episode",
"media_id": 305,
"title": "The One Where No One's Ready",
"series_title": "Friends",
"season_number": 3,
"episode_number": 2,
"poster_path": "/posters/friends.jpg",
"position": 0,
"duration": 1320
}
{
"user_id": 1,
"username": "alice",
"media_type": "movie",
"media_id": 42,
"title": "Inception",
"year": 2010,
"poster_path": "/posters/inception.jpg"
}
{
"media_type": "movie",
"media_id": 42,
"title": "Inception",
"year": 2010,
"overview": "A thief who steals corporate secrets through dream-sharing technology...",
"poster_path": "/posters/inception.jpg",
"library_id": 1,
"library_name": "Movies"
}
{
"library_id": 1,
"library_name": "Movies",
"phase": "complete",
"files_found": 847,
"files_scanned": 847,
"files_added": 3,
"files_removed": 0,
"files_updated": 1,
"progress": 1.0
}

Download Events (download.ready, download.failed)

Section titled “Download Events (download.ready, download.failed)”
{
"job_id": 15,
"user_id": 1,
"media_type": "movie",
"media_id": 42,
"profile": "1080p-h265",
"status": "ready",
"progress": 1.0,
"file_size_bytes": 2147483648
}
{
"task_id": 1024,
"task_type": "probe",
"media_type": "media_file",
"media_id": 305,
"status": "completed"
}