Skip to content

Integrations ​

Phlix supports several integration points with external services. Each integration is configured and managed independently.

Webhooks ​

The Webhooks page (/admin/webhooks) in the admin console provides full CRUD management for webhook subscriptions, plus a per-webhook test trigger.

What it does ​

Webhooks send signed HTTP POST requests to configured endpoints when events occur on the server. Each request carries a JSON payload and is verified with X-Phlix-Signature: sha256=<hmac> so receivers can authenticate the source.

Managing webhooks in the UI ​

ActionHow
ListDataTable showing name, URL, event count badge, and row actions (Edit / Test / Delete)
Add"Add Webhook" button opens a modal with name, URL, secret (with Show/Hide toggle), and event multi-select grouped by category
EditRow action opens the same modal pre-filled. Leave secret blank to keep the current value
DeleteRow action shows a confirm modal, then calls DELETE /api/v1/admin/webhooks/{id}
TestRow action fires POST /api/v1/admin/webhooks/{id}/test and shows the delivery result (green checkmark or red X with message)

The event multi-select lists the 7 subscribable events from the catalog, grouped into 5 categories:

CategoryEvents
Playbackplayback.started, playback.ended
Librarylibrary.updated
Downloadsdownload.complete
Recordingsrecording.started, recording.stopped
Alertsalert

webhook.test is not shown β€” it is used internally by the test button only.

API contract ​

MethodPathDescription
GET/api/v1/admin/webhooksList all webhooks
POST/api/v1/admin/webhooksRegister a new webhook
PUT/api/v1/admin/webhooks/{id}Update name, URL, or events (secret never returned; omit secret field to keep existing)
DELETE/api/v1/admin/webhooks/{id}Remove a webhook
POST/api/v1/admin/webhooks/{id}/testFire a test dispatch

All five are admin-only: 401 {code: auth.required} unauthenticated, 403 {code: auth.not_admin} for a non-admin. The check is taken by AdminMiddleware inside each handler β€” the routes are registered bare in Application::loadWebhookAdminRoutes(), with no route-level middleware group β€” and since S323 the middleware is a required, non-nullable constructor dependency of WebhookAdminController rather than an optional setter with a null guard. See Admin Gate Invariant.

The backend is documented in webhooks.md.

Note on other integrations ​

Arr sync and authentication providers are covered above (step 1.4b). Trakt.tv OAuth connect/disconnect and Last.fm scrobbling connect/disconnect are documented in services.md (step 1.4c).

Arr sync ​

The Arr sync section (/admin/integrations#arr-sync) connects to TRaSH-Guides-compatible indexers (Sonarr/Radarr/Bazarr/Prowlarr) to keep media metadata β€” season packs, quality profiles, and release profiles β€” in sync across yourarr stack.

What it does ​

  • Displays the last sync timestamp (or Never synced if no sync has run yet) and the current enabled/disabled state.
  • A Sync now button manually triggers a POST to pull the latest release profiles from the configured TRaSH-Guides instance.
  • An Enable / Disable toggle controls whether auto-sync runs on schedule.

Managing arr sync in the UI ​

ActionHow
Check statusThe card shows last_sync_at (ISO timestamp) or "Never synced" and an enabled/disabled badge
Sync nowSync now button fires POST /api/v1/admin/sync/trash-guides; button label changes to "Syncing…" with a spinner for the duration; toast on success or error updates the last-sync time
Toggle auto-syncEnable/disable toggle fires PUT /api/v1/admin/sync/enable { enabled: bool }; success toast confirms

API contract ​

MethodPathDescription
GET/api/v1/admin/sync/statusReturns { enabled, last_sync_at, last_sync_timestamp }
POST/api/v1/admin/sync/trash-guidesTriggers a manual TRaSH-Guides sync
PUT/api/v1/admin/sync/enableBody { enabled: bool } β€” enables or disables auto-sync

All three are admin-only: 401 {code: auth.required} unauthenticated, 403 {code: auth.not_admin} for a non-admin. As with the webhook routes above the check is taken by AdminMiddleware inside each handler (Arr\SyncController; the routes are registered bare in Application::loadArrSyncRoutes()), and since S323 the middleware is a required, non-nullable constructor dependency rather than an optional setter with a null guard. See Admin Gate Invariant.

Auth providers ​

The Auth providers section (/admin/integrations#auth-providers) lets admins configure external authentication backends β€” OIDC (OpenID Connect) and LDAP β€” so users can log in with their corporate or identity-provider credentials instead of local Phlix accounts. A third provider, GitHub (plain OAuth 2.0), is available over the admin API but has no card in this UI yet.

Full SSO guide

This page covers the admin configuration UI. For the end-to-end login flow, the callback URL to register with your IdP / GitHub OAuth App (and the PHLIX_DOMAIN requirement behind it), the ldap:-prefix login convention, cookie-based sessions, GitHub setup over the API, and the security posture, see Single Sign-On (OIDC, LDAP & GitHub).

What it does ​

  • Lists all registered auth providers (OIDC, LDAP) with an enable/disable toggle per provider.
  • Expanding a provider reveals its configuration form, pre-filled from the current server settings.
  • LDAP additionally exposes a Test connection button that fires a dry-run POST with the current form values and reports success or failure.

The enable/disable toggle persists a server setting (auth.oidc.enabled / auth.ldap.enabled / auth.github.enabled) and registers or unregisters the provider so the login flow goes live immediately β€” no server restart needed. Other workers pick a change up on their next sign-in request, by comparing a fingerprint of the persisted settings, so a settings save is also effective without a restart. A provider only becomes live when it is both enabled and configured: enabling one that has no saved configuration is rejected with 409 not_configured, so the "Enabled" badge always means "provider live", not merely "setting saved".

Provider configuration is stored in the database β€” one row per provider in the plugin_settings table (migration 093_plugin_settings.sql) β€” not in a per-plugin settings.json. An existing file is imported automatically, once, the first time the provider is read; there is no manual migration step.

A save preserves the fields it does not send (OIDC and GitHub)

The save endpoints replace the whole stored settings document. The OIDC and GitHub endpoints therefore deliberately re-emit any optional key that is absent from the request body, clearing only what is sent as empty. That is why saving from the OIDC form here β€” which has no redirect_uri field β€” does not erase a redirect_uri set through the API. Send "" to clear a field on purpose.

The LDAP endpoint is the exception: apart from bind_pw it rebuilds the document from the body alone, so a scripted partial save resets omitted keys to their defaults. This UI's LDAP form always sends the full set, so it is unaffected β€” but send the complete map if you call that endpoint yourself.

Managing auth providers in the UI ​

ActionHow
List providersProvider cards for OIDC and LDAP show name, whether they are currently enabled, and a Configure expand control
Enable/disableToggle switch per provider fires POST /api/v1/admin/auth-providers/{name}/enable|disable
Configure OIDCClicking Configure expands a form with provider_url, client_id, client_secret (optional, write-only), and scopes; Save fires POST /api/v1/admin/auth-providers/oidc/config
Configure LDAPClicking Configure expands a form with host, port, ssl (switch), base_dn, bind_dn, bind_pw (optional, write-only), user_filter, admin_group; Save fires POST /api/v1/admin/auth-providers/ldap/config; Test connection fires POST /api/v1/admin/auth-providers/ldap/test with the current form values and shows a result toast
Pre-fillAll forms call the GET settings endpoint before rendering so the current values are shown (secrets never returned β€” shown as empty with "(unchanged)" placeholder)

API contract ​

MethodPathDescription
GET/api/v1/admin/auth-providersLists all providers { providers: [{ name, supports_authentication }] }
POST/api/v1/admin/auth-providers/{name}/enableEnables a provider β€” 200 with {enabled, live}, 409 not_configured if it has no saved config, 404 unknown_provider for an unknown name
POST/api/v1/admin/auth-providers/{name}/disableDisables a provider
GET/api/v1/admin/auth-providers/{name}/config-schemaReturns the provider's JSON schema for form rendering
GET/api/v1/admin/auth-providers/oidc/configReturns { provider_url, client_id, scopes, redirect_uri, configured } (never the secret)
POST/api/v1/admin/auth-providers/oidc/configBody { provider_url, client_id, client_secret?, scopes?, redirect_uri? } β€” omitted optional keys keep their stored value; redirect_uri must be an absolute http(s) URL or 400 invalid_redirect_uri
GET/api/v1/admin/auth-providers/oidc/schemaReturns the OIDC config schema
GET/api/v1/admin/auth-providers/github/configReturns { client_id, scopes, redirect_uri, configured } (never the secret; configured = id and secret present)
POST/api/v1/admin/auth-providers/github/configBody { client_id, client_secret?, scopes?, redirect_uri? } β€” same absent-key and absolute-URL rules as OIDC
GET/api/v1/admin/auth-providers/github/schemaReturns the GitHub config schema
GET/api/v1/admin/auth-providers/ldap/configReturns { host, port, ssl, base_dn, bind_dn, user_filter, admin_group, configured }
POST/api/v1/admin/auth-providers/ldap/configBody { host, port, ssl, base_dn, bind_dn, bind_pw?, user_filter, admin_group }
POST/api/v1/admin/auth-providers/ldap/testBody same as LDAP save; returns { success, message }
GET/api/v1/admin/auth-providers/ldap/schemaReturns the LDAP config schema

BSD-3-Clause