Skip to content

Webhooks

chodeus edited this page May 13, 2026 · 7 revisions

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.

The endpoint

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.

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

Auth

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.

Wiring Sonarr

  1. Sonarr → Settings → Connect → + → Webhook.
  2. URL: copy from CHUB's Settings → Webhooks page (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. Use the Reveal button on the CHUB Webhooks page to copy the full header value.
  6. Click Test — Sonarr fires a Test event; CHUB returns 200 and the call shows up under Recent callers on the Webhooks page.
  7. Click Save.

Wiring Radarr

  1. Radarr → Settings → Connect → + → Webhook.
  2. URL: same as Sonarr.
  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.

Wiring Tautulli

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

  1. Settings → Notification Agents → Add a new notification agent → Webhook.
  2. 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.
  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 (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 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.

Recent callers

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.

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 dedup 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 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.
  • 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.

Additional HTTP triggers

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.

Clone this wiki locally