Skip to content

Webhooks

chodeus edited this page Apr 27, 2026 · 4 revisions

Webhooks

CHUB can react to Sonarr, Radarr, and Tautulli events so poster rename and cleanup jobs fire the moment a new item arrives, instead of waiting for the next scheduled run. Every webhook URL is under /api/webhooks/*.

Auth

Webhook 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: ?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.

The five endpoints

POST /api/webhooks/poster/add

Who calls it: Sonarr / Radarr / Tautulli on a media event. Sonarr and Radarr are the primary callers; Tautulli is useful if you want one trigger source that also covers Plex-side adds Sonarr/Radarr don't see.

What CHUB does: validates the payload, deduplicates against the persistent webhook_cache (600-second rolling TTL keyed on the media identity hash — survives restarts, so retries from a Sonarr/Radarr instance that timed out earlier still get coalesced), then enqueues a poster-processing job scoped to the single item in the payload. The job goes through the same pipeline a scheduled run uses: rename → border replacer (if enabled) → Plex upload.

Accepted event types: 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

Season-aware import: when a Sonarr Download or EpisodeFileImported payload carries episodes[*].seasonNumber, CHUB extracts it and 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.

GET /api/webhooks/unmatched/status

Returns the current unmatched-assets summary — how many posters don't have a media match. Useful for dashboards and uptime probes.

POST /api/webhooks/unmatched/process

Enqueues an unmatched_assets run to refresh the report. Anything that can send an HTTP POST can trigger this (cron, Home Assistant, a shortcut on your phone).

GET /api/webhooks/cleanarr/status

Returns the current orphaned-poster count.

POST /api/webhooks/cleanarr/process

Enqueues a poster_cleanarr run.

Wiring Sonarr

  1. Open Sonarr → Settings → Connect → + → Webhook.
  2. URL: http://<chub-host>:8000/api/webhooks/poster/add
  3. Method: POST
  4. Notification Triggers: check On Import, On Upgrade, and On Series Add. Leave the rest unchecked — CHUB ignores them.
  5. Headers: if you set a webhook secret, add X-Webhook-Secret: <your secret> here.
  6. Click Test — Sonarr fires a Test event; CHUB returns 200 and records the origin under Settings → Webhooks → Recent origins.
  7. Click Save.

Wiring Radarr

  1. Open Radarr → Settings → Connect → + → Webhook.
  2. URL: http://<chub-host>:8000/api/webhooks/poster/add
  3. Method: POST
  4. Notification Triggers: check On Import, On Upgrade, and On Movie Added. Leave the rest unchecked.
  5. Headers: same X-Webhook-Secret as Sonarr if you set a secret.
  6. Click Test then Save.

Tip: CHUB's Settings → Webhooks page generates ready-to-paste URLs with the secret already applied.

Wiring Tautulli

  1. Settings → Notification Agents → Add a new notification agent → Webhook.
  2. URL: http://<chub-host>:8000/api/webhooks/poster/add
  3. Triggers: Recently Added is usually enough.
  4. Data format: JSON. Leave the template blank unless you know what you're overriding.

Forcing re-uploads on a per-instance basis

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:

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 false

The flag only affects webhook-triggered uploads from that instance. Scheduled and manual runs still respect the hash-skip.

Plex availability retry

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 attempts

If 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.

Origin tracking

Every webhook-created job records who called it — the source host, the endpoint hit, the event type, and the user agent. Settings → Webhooks → Recent origins shows a rollup of the last 7 days: useful for spotting a noisy integration or catching an unexpected caller.

Inbound retries

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 webhook_cache keeps duplicate retries from re-firing the pipeline within the 600-second TTL.

Troubleshooting

  • 401 Unauthorized — your webhook_secret is set but the caller isn't sending the header or ?secret=. Copy the URL from Settings → Webhooks to get a version with the secret already applied.
  • 404 Not Found — path typo. Every endpoint is under /api/webhooks/*, not /webhook/*.
  • 200 but 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 debounced at 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_availability retry budget (5.5 min by default) may have run out before Plex finished scanning. Either bump webhook_max_retries or wait for the next scheduled rename.

See Troubleshooting for more.

Clone this wiki locally