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.
Setting Up a Webhook
Section titled “Setting Up a Webhook”- Go to Admin > Webhooks.
- Click Add Webhook.
- Choose a type: HTTP or Discord.
- Enter the endpoint URL.
- Select which events to subscribe to.
- Click Create.
The webhook will begin delivering events immediately.
Webhook Types
Section titled “Webhook Types”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:
| Header | Description |
|---|---|
Content-Type | Always application/json |
User-Agent | Odeion-Webhooks/1.0 |
X-Odeion-Event | The event type (e.g. playback.started) |
X-Odeion-Delivery | Unique delivery ID for deduplication |
X-Odeion-Signature | HMAC-SHA256 signature (only if a secret is configured) |
HMAC Verification
Section titled “HMAC Verification”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:
- Compute
HMAC-SHA256(secret, raw_request_body). - Compare the hex digest against the value after
sha256=.
This prevents spoofed deliveries if your endpoint is publicly reachable.
Custom Headers
Section titled “Custom Headers”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
Section titled “Discord”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:
- Open your Discord server settings.
- Go to Integrations > Webhooks.
- Click New Webhook, choose a channel, and copy the URL.
- 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.
Event Types
Section titled “Event Types”Events are grouped into categories. You can subscribe to any combination per webhook.
Playback
Section titled “Playback”| Event | Fires when |
|---|---|
playback.started | A user begins playing a movie or episode |
playback.stopped | A user stops playback |
media.watched | A 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.
Library
Section titled “Library”| Event | Fires when |
|---|---|
media.added | A new movie or series is added and matched to metadata |
scan.completed | A 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).
Downloads
Section titled “Downloads”| Event | Fires when |
|---|---|
download.ready | A transcoded download is ready for the user to collect |
download.failed | A transcode/download job failed |
System
Section titled “System”| Event | Fires when |
|---|---|
task.completed | A background processing task finishes successfully |
task.failed | A background processing task fails |
System events include the task type (probe, associate, media_extract, etc.) and error details for failures.
Retries and Delivery
Section titled “Retries and Delivery”When a delivery fails (network error, non-2xx response), Odeion retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 10 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 90 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.
Delivery Log
Section titled “Delivery Log”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.
Event Payload Reference
Section titled “Event Payload Reference”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}Media Watched (media.watched)
Section titled “Media Watched (media.watched)”{ "user_id": 1, "username": "alice", "media_type": "movie", "media_id": 42, "title": "Inception", "year": 2010, "poster_path": "/posters/inception.jpg"}Media Added (media.added)
Section titled “Media Added (media.added)”{ "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"}Scan Completed (scan.completed)
Section titled “Scan Completed (scan.completed)”{ "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 Events (task.completed, task.failed)
Section titled “Task Events (task.completed, task.failed)”{ "task_id": 1024, "task_type": "probe", "media_type": "media_file", "media_id": 305, "status": "completed"}