Skip to content

Seerr Notifications

RadicalMuffinMan edited this page Sep 7, 2026 · 2 revisions

Seerr Notifications

Moonbase can tell people what is happening with their Seerr requests and issues, right inside the Moonfin apps instead of over email or Discord. Someone submits a request, an admin approves it, the movie lands in the library, and the people who care about that event hear about it.

What you need

  • Moonbase installed on Jellyfin or Emby, with Seerr enabled and its URL set in the admin panel.
  • Each user signed in to Seerr at least once through a Moonfin client, so the server holds a session for them. A user with no session can't be targeted.
  • A server address Seerr can actually reach, since Seerr is the one calling your server. A plain LAN address like http://192.168.1.10:8096 works fine, no proxy needed.
  • If you run behind a reverse proxy, it has to forward every /Moonfin/ path, including /Moonfin/Seerr/Webhook. See Reverse Proxy and Seerr.

Nothing extra is needed for phone notifications. Push works out of the box through the hosted relay.

How it works

Seerr fires a webhook at your server whenever something notable happens. Moonbase reads it, works out which users should hear about it, checks their preferences and Seerr permissions, and delivers on two paths at once:

  1. The live stream. Any client that's open and connected gets the notification immediately and draws it on screen.
  2. Push. For phones and tablets, the same notification also goes out over Firebase Cloud Messaging, so a backgrounded or fully closed app still shows it through the operating system.

Both paths are gated by the same per-user preferences, so turning something off turns it off everywhere. Duplicates are suppressed for a minute, so a burst of webhook traffic for one title doesn't become a stack of identical notifications.

Which events turn into notifications

Seerr event Shows as Goes to
MEDIA_PENDING New request Everyone who can manage requests in Seerr, apart from the requester
MEDIA_APPROVED Request approved The requester
MEDIA_DECLINED Request declined The requester
MEDIA_AVAILABLE Now available The requester, with the season number when it's a partial series request
ISSUE_CREATED New issue Everyone who can manage issues in Seerr, apart from the reporter
ISSUE_COMMENT Issue comment The other side of the thread. Nobody is notified about their own comment
ISSUE_RESOLVED Issue resolved The reporter, unless they resolved it themselves
ISSUE_REOPENED Issue reopened The reporter, unless they reopened it themselves

Permissions come from Seerr, not from your media server. Manage Requests, Manage Issues, Admin, and the Seerr owner account are what make someone a manager.

Tapping a notification takes you somewhere useful. Request events open the Seerr detail page for that title, an availability notification opens the item in your library, and issue events open the Issues tab on the requests screen.

Server setup

Moonbase registers its own webhook in Seerr for you. Once an admin has signed in to Seerr through Moonfin, the plugin writes the webhook URL, secret, payload template, and event list into Seerr's webhook agent. It only writes when something is actually different.

The one setting that trips people up is Public Server URL. Seerr has to reach your server, and the server can only guess its own address. If Seerr and your server live in separate containers or on separate hosts, set Public Server URL in the Moonbase admin panel to a base URL Seerr can hit, for example http://192.168.1.10:8096 or https://jellyfin.example.com.

The admin panel shows the current provisioning status next to that field, along with a Re-provision webhook button that writes the webhook again immediately. Use it after you change the public URL.

Advanced: the foreign webhook guardrail

If Seerr already has a different webhook configured, Moonbase leaves it alone and reports that it found a foreign webhook, because Seerr only has one webhook agent slot and quietly overwriting somebody's existing integration would be rude. In that case, set Moonfin's webhook up by hand, or move the other integration elsewhere.

Advanced: how the server guesses its address

Moonbase tries the admin override first, then your published server URL, then a LAN address, and only falls back to a loopback address as a last resort. Loopback is useless to a Seerr running in another container, so if that fallback is what got used, the admin panel flags it.

Admins can also read the details straight from the API:

GET /Moonfin/Notifications/WebhookInfo
POST /Moonfin/Notifications/Reprovision

WebhookInfo returns the full webhook URL including its secret, the header name if you would rather send the secret that way, the list of event types Moonfin wants, the last provisioning status, whether the resolved URL looks unreachable, and the event bitmask Seerr currently has stored.

Advanced: setting the webhook up by hand

If auto registration can't run, open Seerr's Settings > Notifications > Webhook and fill it in yourself:

  • Webhook URL: the URL from WebhookInfo, which is your public base URL plus /Moonfin/Seerr/Webhook?secret=.... The secret can travel as the X-Moonfin-Webhook-Secret header instead if you prefer to keep it out of the query string. Requests without a valid secret are rejected.
  • Notification types: Request Pending, Request Approved, Request Declined, Request Available, Issue Reported, Issue Comment, Issue Resolved, and Issue Reopened.
  • JSON payload: Moonbase reads specific keys, so use the same template it writes automatically:
{
    "notification_type": "{{notification_type}}",
    "subject": "{{subject}}",
    "message": "{{message}}",
    "notifyuser_username": "{{notifyuser_username}}",
    "{{media}}": {
        "media_type": "{{media_type}}",
        "tmdbId": "{{media_tmdbid}}",
        "tvdbId": "{{media_tvdbid}}",
        "status": "{{media_status}}"
    },
    "{{request}}": {
        "request_id": "{{request_id}}",
        "requestedBy_username": "{{requestedBy_username}}",
        "requestedBy_jellyfinUserId": "{{requestedBy_jellyfinUserId}}"
    },
    "{{issue}}": {
        "issue_id": "{{issue_id}}",
        "issue_type": "{{issue_type}}",
        "issue_status": "{{issue_status}}",
        "reportedBy_username": "{{reportedBy_username}}"
    },
    "{{comment}}": {
        "comment_message": "{{comment_message}}",
        "commentedBy_username": "{{commentedBy_username}}"
    },
    "{{extra}}": []
}

requestedBy_jellyfinUserId is the one that matters most. It lets Moonbase target the requester directly instead of guessing from a username, so drop it and availability notifications get less reliable.

Advanced: push delivery and self-hosting

Push is what gets a notification onto a phone whose Moonfin app is closed. Everything else already works over the live stream.

By default Moonbase sends through the hosted Moonfin relay, so there is nothing to configure. The admin panel fields are all optional:

  • Push Relay URL: leave the default unless you run your own relay.
  • Push Relay Key: leave blank unless you were given a key of your own.
  • Push Service Account and Push Service Account File Path: paste or point at a Firebase service account JSON to send directly through your own Firebase project instead of the relay. Generate it in the Firebase console under Project Settings > Service accounts. A configured service account takes priority over the relay, and the file path takes priority over pasted JSON.

Devices register their push token with the server when they sign in, and the token is dropped again on sign-out. Tokens Firebase reports as dead are pruned automatically, so a reinstalled or wiped device stops being a delivery target on its own.

User preferences

Each user controls what they hear about. The preferences live on the server rather than on the device, so a toggle flipped on a phone also governs what shows up on the TV.

In the Moonfin apps, open Settings > Integrations > Seerr (or whatever your Seerr instance is named) and look for the Notifications section:

  • New request notifications: alerts when somebody submits a request. Only appears for users who can manage requests in Seerr, and starts off.
  • Request updates: approved, declined, and added to your library. On by default.
  • Issue updates: new issues, replies, and resolutions. On by default.

Turning any of them on for the first time on a phone or tablet also asks for the operating system notification permission.

The same preferences are available over the API: GET and POST /Moonfin/Notifications/Prefs.

How notifications look on each client

Everywhere: a floating banner slides in with the title and body, sits for about seven seconds, and fades out. On phones, tablets, and desktop the banner is tappable. On TVs it's deliberately passive, so it never steals focus from the remote.

Phones and tablets also get a real operating system notification. New request notifications carry Approve and Deny buttons, so a decision can be made without opening the app. Tapping a notification while the app is closed launches it on the right screen.

Desktop, Apple TV, and Android TV show the in-app banner only, and only while the app is open. There's no push on these platforms, so anything that happens while the app is closed is caught up on next time you open it.

The Smart TV app (Tizen and webOS) shows a small passive toast in the corner. Those platforms have no OS notification surface for third-party apps, so notifications arrive only while the app is open. The TV has no toggles of its own, it follows the preferences stored on the server.

Advanced: mobile permission details
  • Android 13 and newer requires the POST_NOTIFICATIONS permission. The app asks for it when you turn on a notification toggle. If you dismissed the prompt, grant it under the system app settings for Moonfin, since Android won't ask twice.
  • iOS and iPadOS ask for alert, badge, and sound permission the same way. Denied permission means banners inside the app still work while it's open, but nothing shows when it isn't.
  • Official builds ship with the push capability and the APNs key wired up. If you build the iOS app from source, you need your own Firebase project and APNs key, otherwise the device never gets a push token and only in-app banners work.
  • The Approve and Deny buttons use the running app's session when it's alive, and rebuild one from stored credentials when the app has been killed. If a decision fails, for example because the server is unreachable, a short "Couldn't approve" or "Couldn't deny" notification comes back with the reason.
  • When the app is fully closed, iOS still renders the action buttons inline because the push carries the matching notification category, while Android shows a plain notification that opens the app when tapped.

Troubleshooting

Nothing arrives anywhere. Check the provisioning status in the admin panel first. If it reports a foreign webhook, Seerr's single webhook slot is taken by something else. If it reports that no admin session is available, sign in to Seerr through Moonfin as a Seerr admin and try again.

The status looks fine but no notifications land. Seerr can't reach your server. Set Public Server URL to an address Seerr can actually hit, which is often just your LAN address and port, and re-provision. If you're behind a reverse proxy, also check that it forwards /Moonfin/Seerr/Webhook. Seerr's own logs show the failed webhook call.

Some users get them and others don't. Users only become targets once Moonbase has a stored Seerr session for them, so anyone who has never signed in to Seerr through Moonfin is invisible to the matching logic. Beyond that, check their notification toggles and their Seerr permissions.

Availability notifications don't reach the requester. Confirm your webhook payload includes requestedBy_jellyfinUserId. Re-provisioning rewrites the template for you.

In-app banners work but nothing shows when the app is closed. That's push. Confirm the device granted notification permission, that it signed in after push was set up so its token is registered, and that you're on a phone or tablet rather than a TV or desktop.

Duplicate notifications. Repeats of the same event for the same title are collapsed for a minute. Anything past that window is treated as a genuinely new event.

See API Reference for the full endpoint list.

Clone this wiki locally