Skip to content

feat: Jellyseerr webhook as alternative notification source#86

Merged
retardgerman merged 7 commits intomainfrom
dev
Mar 23, 2026
Merged

feat: Jellyseerr webhook as alternative notification source#86
retardgerman merged 7 commits intomainfrom
dev

Conversation

@retardgerman
Copy link
Contributor

@retardgerman retardgerman commented Mar 21, 2026

Summary

  • Adds `POST /seerr-webhook` endpoint that handles `MEDIA_AVAILABLE` events from Jellyseerr's built-in webhook notification system
  • Uses the same `WEBHOOK_SECRET` as the Jellyfin endpoint (set via `Authorization` header in Jellyseerr)
  • Enabled/disabled via `SEERR_WEBHOOK_ENABLED` toggle in the dashboard
  • Sends rich Discord embeds with TMDB backdrop, poster thumbnail, IMDb/Letterboxd buttons, and a Jellyfin search "Watch Now" link
  • Supports DM notifications for users who requested the content via `/request`
  • Notifications go to the configured `JELLYFIN_CHANNEL_ID`
  • Single episode notifications now show the series poster as thumbnail and the episode description

Closes #84

Documentation written with AI assistance; all code manually verified.

Adds POST /seerr-webhook that handles MEDIA_AVAILABLE events from Jellyseerr's built-in webhook, as an alternative to the Jellyfin webhook plugin. Uses the same WEBHOOK_SECRET (Authorization header), sends to JELLYFIN_CHANNEL_ID, and supports TMDB metadata, IMDb/Letterboxd buttons, Jellyfin search Watch Now link, and DM notifications for pending requests.
Seerr webhook now resolves the target Discord channel by matching the
Jellyfin library CollectionType (movies/tvshows) against
JELLYFIN_NOTIFICATION_LIBRARIES, instead of always sending to the
hardcoded JELLYFIN_CHANNEL_ID. Falls back to JELLYFIN_CHANNEL_ID when
no library match is found or no libraries are configured.

Library list is cached for 10 minutes to avoid repeated N+1 Jellyfin
API calls on every webhook invocation.
- Log error message when Jellyfin library fetch fails in resolveChannelForMediaType
- Warn when library channels are configured but Jellyfin credentials are missing
- Include error detail in TMDB fetch failure log
- Replace empty catch block in findLibraryByAncestors with debug log
- Include imdbId in OMDb failure log
- Include value preview in JELLYFIN_NOTIFICATION_LIBRARIES parse failure log
Introduces BIND_HOST env var (default 127.0.0.1) so bare-metal installs
no longer expose port 8282 on all network interfaces. Docker Compose sets
BIND_HOST=0.0.0.0 so container port mapping continues to work.

- Validates BIND_HOST with net.isIP() before listen, exits early with an
  actionable error message on invalid values
- Adds dedicated EADDRNOTAVAIL branch in the server error handler
- Registers BIND_HOST in the Joi validation schema and DEFAULTS constant
- Add note that bare-metal binds to 127.0.0.1 by default and how to
  override with BIND_HOST
- Add BIND_HOST=0.0.0.0 to all manual Docker run and Docker Hub examples
- Fix incorrect security claim: brute-force lockout has been in place
  since v1.4.3
- Add .markdownlint.json to suppress false positives (duplicate headings
  in changelog, inline HTML in contributors table, long lines)
- Remove partial API key from debug log in seerr request handler
- Fix ReDoS in cleanTitle(): drop leading \s* from bracket-ID regexes,
  trim whitespace separately
- Add botControlLimiter (10 req/min) to /start-bot and /stop-bot routes
- Replace regex-based sanitizeTranslationHtml() with DOM-based allowlist
  parser (closes bypass vectors for nested tags and non-standard close tags)
- Add permissions: contents: read to docker-publish.yml workflow
- Re-parse URLs through new URL() after getSeerrApiUrl() in config-test
  routes so CodeQL can trace the sanitized value to the axios call site
// Regex-based HTML stripping is inherently bypass-prone; parsing via the
// browser's own HTML parser is the only reliable approach.
const div = document.createElement("div");
div.innerHTML = str;

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.
@retardgerman retardgerman merged commit 76ae462 into main Mar 23, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature request] Alternative webhook options

1 participant