How to Receive Webhooks from Sonarr and Radarr (Self-Hosted, Step by Step)

Receive Sonarr, Radarr, and other *arr Connect webhook notifications on your own server with HTTP Basic authentication, Docker setup, and RabbitMQ routing, verified end to end against a real Sonarr v4.

By the end of this guide you will have a self-hosted endpoint that receives Sonarr webhook notifications, authenticates every delivery with HTTP Basic credentials, and routes the events to a log, RabbitMQ, or any other destination. Everything below was verified end to end against a real Sonarr v4 instance and the real Core Webhook Module Docker image: Sonarr's own test mechanism sent the deliveries quoted here. The same setup works for the whole *arr family - Radarr, Lidarr, Prowlarr, Readarr - because they share the identical Webhook connection type.

How Sonarr webhooks work

Sonarr notifies external systems through Connections (also called Connect notifications). The Webhook connection POSTs a JSON payload to your URL when the events you tick happen: On Grab, On Import, On Upgrade, On Rename, On Series Delete, On Health Issue, and more. Every payload carries an eventType field naming the event, plus the relevant objects. The test event, captured from a real delivery, looks like this:

{
  "series": {
    "id": 1,
    "title": "Test Title",
    "path": "C:\\testpath",
    "tvdbId": 1234,
    "type": "standard",
    "tags": ["test-tag"]
  },
  "episodes": [
    {"id": 123, "episodeNumber": 1, "seasonNumber": 1, "title": "Test title"}
  ],
  "eventType": "Test",
  "instanceName": "Sonarr",
  "applicationUrl": ""
}

Real events replace eventType with Grab, Download, HealthIssue, and so on, and fill in the actual series, episode, release, or health data. The request arrives with a User-Agent like Sonarr/4.0.19.2979 and Content-Type: application/json.

Authentication: Sonarr does not sign webhook payloads. The Webhook connection form has these fields (taken from Sonarr's own API schema): Webhook URL, Method (POST or PUT), Username, Password, and Headers. Username and Password become standard HTTP Basic authentication on every delivery, which the gateway verifies with a constant-time comparison. Newer versions can also attach custom headers via the Headers key-value list if you prefer token-style auth.

Prerequisites

Step 1: Configure the receiver

Create a working directory with two files.

webhooks.json:

{
    "sonarr_events": {
        "data_type": "json",
        "module": "log",
        "basic_auth": {
            "username": "{$SONARR_WEBHOOK_USER}",
            "password": "{$SONARR_WEBHOOK_PASS}"
        }
    }
}

connections.json:

{}

What each parameter does:

  • sonarr_events is the webhook ID; it becomes the URL path (/webhook/sonarr_events).
  • module: log prints each authenticated event to the container log. Zero dependencies for the first run; we swap in RabbitMQ at the end.
  • basic_auth validates standard HTTP Basic credentials (RFC 7617) with constant-time comparison.
  • The {$...} placeholders pull both credentials from environment variables, so they never live in a config file.

Generate a strong password (or use our webhook secret generator):

openssl rand -hex 24

Step 2: Run it

docker run -d --name webhook-gateway -p 8000:8000 \
  -v "$PWD/webhooks.json:/app/webhooks.json:ro" \
  -v "$PWD/connections.json:/app/connections.json:ro" \
  -e SONARR_WEBHOOK_USER="gateway" \
  -e SONARR_WEBHOOK_PASS="your-generated-password" \
  spiderhash/webhook:latest

Confirm it is up:

docker logs webhook-gateway
# ... INFO: Application startup complete.

The receiver now answers on http://localhost:8000/webhook/sonarr_events, with auto-generated API docs at http://localhost:8000/docs.

Step 3: Test it locally

Simulate exactly what Sonarr sends. Save the test payload from the top of this article as test_event.json, then:

curl -s -w "\nHTTP %{http_code}\n" -X POST http://localhost:8000/webhook/sonarr_events \
  -u gateway:your-generated-password \
  -H "Content-Type: application/json" \
  --data-binary @test_event.json

Expected result:

{"message":"200 OK"}
HTTP 200

Now prove the authentication works. Wrong credentials:

{"detail":"Invalid credentials"}
HTTP 401

And a request with no credentials at all:

{"detail":"Missing Authorization header"}
HTTP 401

Those are the exact responses the gateway returns. docker logs webhook-gateway shows the accepted payload.

Step 4: Create the connection in Sonarr

In Sonarr: Settings > Connect > + > Webhook:

  • Name: something like webhook-gateway
  • Notification Triggers: tick the events you want (On Grab and On Import are the usual starting point)
  • Webhook URL: https://your-domain/webhook/sonarr_events
  • Method: POST
  • Username: gateway (the value of SONARR_WEBHOOK_USER)
  • Password: the value of SONARR_WEBHOOK_PASS

Press Test: Sonarr sends the eventType: "Test" payload immediately, and the button goes green when your endpoint answers 200. Save. In Radarr, Lidarr, Prowlarr, and Readarr the form is the same, with series/episodes replaced by that app's objects (movie in Radarr, and so on); give each app its own webhook ID on the gateway to keep the streams separate.

Troubleshooting

  • 401 {"detail":"Invalid credentials"}: the Username/Password in the connection and the gateway's env vars differ. They must be byte-identical.
  • 401 {"detail":"Missing Authorization header"}: the Username and Password fields in Sonarr are empty. Fill both; Sonarr only sends the Authorization header when credentials are configured.
  • Test button fails instantly: Sonarr must be able to reach the URL. From a Docker-based Sonarr, localhost points at the Sonarr container itself, not the host; use the host's address or a shared Docker network.
  • Events fire but with unexpected shapes: every event type carries different objects. Branch on eventType in your consumer, and treat Test as a no-op. Health issue events, for example, carry a healthCheck object instead of series.

Going to production

Printing to logs is not a pipeline. Swap the module for a real destination; here is the RabbitMQ variant, verified end to end (a real Sonarr test delivery landed as a message in the sonarr_events queue):

webhooks.json:

{
    "sonarr_events": {
        "data_type": "json",
        "module": "rabbitmq",
        "connection": "rabbitmq_local",
        "module-config": {
            "queue_name": "sonarr_events"
        },
        "basic_auth": {
            "username": "{$SONARR_WEBHOOK_USER}",
            "password": "{$SONARR_WEBHOOK_PASS}"
        }
    }
}

connections.json:

{
    "rabbitmq_local": {
        "type": "rabbitmq",
        "host": "{$RABBITMQ_HOST:localhost}",
        "port": "{$RABBITMQ_PORT:5672}",
        "user": "{$RABBITMQ_USER:guest}",
        "pass": "{$RABBITMQ_PASS:guest}"
    }
}

The same pattern works for 17 other destinations: archive your media pipeline history to S3, publish to Kafka, or fan out to several at once with webhook chaining. Built-in rate limiting and retry handling cover the operational edges; a season pack import that fires a burst of events is exactly when acknowledging fast and queueing durably pays off. The full authentication reference, including Basic auth, header auth, and 10 other methods, is in the docs.

Subscribe to Free Webhook Tool

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe