-
Notifications
You must be signed in to change notification settings - Fork 0
Webhooks
CHUB exposes one inbound webhook so Sonarr, Radarr, or Tautulli can push fresh posters the moment a new item lands — no waiting for the next scheduled run. The wiring lives entirely on the *arr side; CHUB's Settings → Webhooks page just builds the URL for you.
If you're here to fire a module run from external automation (cron, Home Assistant, a phone shortcut), jump to Additional HTTP triggers at the bottom.
POST http://<chub-host>:8000/api/webhooks/poster/add
That's the whole UI surface. CHUB's Settings → Webhooks page renders this URL with your current origin, plus a one-click copy button.
What happens on a call: the payload is validated, deduplicated against a persistent 600-second rolling cache (keyed on media identity — survives restarts), and a poster-processing job is enqueued scoped to the single item in the payload. The job runs the same pipeline a scheduled poster_renamerr would: rename → border replacer (if enabled) → Plex upload.
Sonarr Download / EpisodeFileImported are season-aware: when the payload includes episodes[*].seasonNumber, CHUB narrows the rename pass to (show row + matching season row) instead of re-walking every season's assets. One imported episode → one season's posters re-checked.
Only events that correspond to a new on-disk file are processed. Anything else gets a 200 ack and is dropped.
| Event | Sonarr trigger label | Radarr trigger label | Processed? |
|---|---|---|---|
Download |
On Import / On Upgrade | On Import / On Upgrade | ✅ |
EpisodeFileImported |
On Import (v4+) | — | ✅ |
MovieFileImported |
— | On Import (v5+) | ✅ |
SeriesAdd |
On Series Add | — | ✅ |
MovieAdded |
— | On Movie Added | ✅ |
Test |
(Test button) | (Test button) | ✅ acked, no job |
Grab |
On Grab | On Grab | 🚫 ignored — file isn't on disk yet |
Rename |
On Rename | — | 🚫 ignored |
*FileDelete, *Delete, HealthIssue, ApplicationUpdate, etc. |
On … | On … | 🚫 ignored |
Authentication is optional and off by default. If you set general.webhook_secret in config.yml (or via Settings → General in the UI), every inbound request has to prove it knows the secret:
-
Preferred:
X-Webhook-Secret: <secret>HTTP header. -
Fallback for services that can't send custom headers (Tautulli, some shortcut apps):
?secret=<secret>query parameter.
A wrong or missing secret returns 401. If webhook_secret is empty, webhooks run unauthenticated — fine on a trusted LAN, but don't expose unauthenticated webhook URLs to the open internet.
Settings → Webhooks masks the secret by default and exposes a Reveal/Hide toggle plus copy buttons for the header form, the ?secret= URL form, and the plain URL.
- Sonarr → Settings → Connect → + → Webhook.
-
URL: copy from CHUB's Settings → Webhooks page (
http://<chub-host>:8000/api/webhooks/poster/add). -
Method:
POST. - Notification Triggers: check On Import, On Upgrade, and On Series Add. Leave the rest unchecked — CHUB ignores them.
-
Headers: if you set a webhook secret, add
X-Webhook-Secret: <your secret>here. Use the Reveal button on the CHUB Webhooks page to copy the full header value. - Click Test — Sonarr fires a
Testevent; CHUB returns200and the call shows up under Recent callers on the Webhooks page. - Click Save.
- Radarr → Settings → Connect → + → Webhook.
- URL: same as Sonarr.
-
Method:
POST. - Notification Triggers: check On Import, On Upgrade, and On Movie Added. Leave the rest unchecked.
-
Headers: same
X-Webhook-Secretas Sonarr if you set a secret. - Click Test then Save.
Tautulli is useful as a single trigger source that also covers Plex-side adds Sonarr/Radarr don't see (manual uploads, Plex Watchlist auto-adds, etc.).
- Settings → Notification Agents → Add a new notification agent → Webhook.
-
URL: Tautulli doesn't have a Headers field, so use the
?secret=URL form from CHUB's Webhooks page (Reveal → copy the "Fallback — URL with?secret=query param" row). If no secret is configured, the plain URL is fine. - Triggers: Recently Added is usually enough.
- Data format: JSON. Leave the template blank unless you know what you're overriding.
By default CHUB hashes every poster file on disk and skips the upload if the hash matches what was last pushed to Plex — so a webhook for an unchanged poster is a no-op. If a Plex instance has been wiped or you're actively re-curating a library and want every webhook to re-push regardless, set webhook_force_reupload: true on the originating *arr instance (configurable on the Settings → Instances page or in config.yml):
instances:
sonarr:
main:
url: http://sonarr:8989
api: ...
webhook_force_reupload: true # webhooks from this Sonarr always re-push
radarr:
main:
url: http://radarr:7878
api: ...
# webhook_force_reupload omitted → defaults to falseThe flag only affects webhook-triggered uploads from that instance. Scheduled and manual runs still respect the hash-skip.
After enqueuing the job, CHUB polls each configured Plex section's recently-added list before pushing posters — Plex's library scan can lag several minutes behind a Sonarr/Radarr import. Defaults give a ~5.5-minute search window:
general:
webhook_initial_delay: 30 # seconds to wait before the first Plex check
webhook_retry_delay: 30 # seconds between retries
webhook_max_retries: 10 # total attemptsIf the item still isn't found after every retry, the upload step is skipped for this run; the next scheduled rename will pick it up.
Every webhook-created job records who called it — source host, endpoint, event type, user agent. The Settings → Webhooks page renders the last 7 days as a rollup of client_host → endpoint with call counts and a status breakdown. Useful for spotting a noisy integration, catching an unexpected caller, or confirming a freshly wired *arr is actually reaching CHUB.
If CHUB enqueues a job but the underlying module later fails, the webhook doesn't retry the inbound call — the job lives in the normal queue and follows its own retry rules. If the inbound webhook call itself errors (for instance, CHUB is mid-restart), Sonarr/Radarr will retry per their own settings, and the persistent dedup cache keeps duplicate retries from re-firing the pipeline within the 600-second TTL.
-
401 Unauthorized— yourwebhook_secretis set but the caller isn't sending the header or?secret=. Copy from Settings → Webhooks to get a URL/header with the secret already applied. -
404 Not Found— path typo. The endpoint is/api/webhooks/poster/add, not/webhook/...or/webhooks/poster-add. -
200but nothing happens — three possibilities:- The event type isn't on the allow-list (e.g. you checked On Grab in Sonarr — files aren't on disk yet, so CHUB silently 200s).
- The same item fired within the 600-second dedup window. Look for
Duplicate webhook debouncedat debug level. - The job ran but failed downstream. Check Settings → Jobs, filter by module and status.
-
Sonarr says "Test connection failed" — almost always a network issue: Sonarr can't reach the CHUB host/port. Verify from inside Sonarr's container:
curl -I http://chub:8000/api/health. -
A new download didn't get its poster pushed to Plex — the
wait_for_plex_availabilityretry budget (5.5 min by default) may have run out before Plex finished scanning. Either bumpwebhook_max_retriesor wait for the next scheduled rename.
See Troubleshooting for more.
These endpoints aren't inbound webhooks in the Sonarr/Radarr sense — they're plain HTTP triggers for module runs that you can fire from external automation (cron, Home Assistant, a phone shortcut). Each is rate-limited and honours general.webhook_secret the same way as /poster/add. CHUB's UI runs the matching modules from their own dedicated pages instead, so these are only relevant if you want to fire them from outside CHUB.
| Method | Endpoint | What it does |
|---|---|---|
GET |
/api/webhooks/unmatched/status |
Return the current unmatched-assets summary. |
POST |
/api/webhooks/unmatched/process |
Enqueue an unmatched_assets run. |
GET |
/api/webhooks/wiring |
Return the poster/add path + optional secret — used by CHUB's own UI to build URLs. |
GET |
/api/jobs/webhook-origins?days=7 |
Summarize recent webhook calls by client_host + endpoint. |