# Watcher

Watcher does real-time scrobbling in CrossWatch (CW).

It reads playback from your media server.

It pushes progress to trackers.

Watcher uses **routes mode** (starting in **v0.9.0**).

Routes are many-to-many mappings.

You can run multiple media servers and trackers together.

### What it supports

Sources (providers):

* Plex
* Emby
* Jellyfin

Targets (sinks):

* Trakt
* SIMKL
* MDBList

Events:

* now playing (progress)
* pause/stop
* completion (after thresholds)

### Routes mode (the important part)

Each route is:

`provider(profile) → sink(profile)`

That unlocks:

* Plex + Jellyfin + Emby in parallel.
* Trakt + SIMKL + MDBList at the same time.
* Multiple accounts per provider via profiles.

Profiles are called **Profiles** in the UI.

Profiles are documented here:

* [Profiles](/crosswatch/profiles.md)

<figure><img src="/files/Uir9L7K8S3DwQxDbURhQ" alt=""><figcaption></figcaption></figure>

### How sources are read (high level)

Watcher reads sources like this:

* **Plex**: connects via `AlertListener` and consumes “Playing” alerts.
* **Emby**: polls `/Sessions?ActiveWithinSeconds=15` (every \~15s).
* **Jellyfin**: polls `/Sessions?ActiveWithinSeconds=15` (every \~15s).

### Prerequisites

* Connect your media server in **Settings → Authentication**.
* Connect at least one target tracker in **Settings → Authentication**.

{% hint style="info" %}
**Tip:** If you use Emby or Jellyfin, connect Trakt too.\
Even if you don’t scrobble to Trakt, CW can use it as an ID fallback.
{% endhint %}

### Quick start (recommended)

{% stepper %}
{% step %}

### Connect providers

1. Connect a media server (Plex/Emby/Jellyfin).
2. Connect one or more targets (Trakt/SIMKL/MDBList).
3. If needed, create provider profiles.
   {% endstep %}

{% step %}

### Create routes

1. Open **Settings → Scrobble → Watcher**.
2. Add one route per mapping you want. Example: `Plex → Trakt`, `Plex → SIMKL`, `Jellyfin → MDBList`.
3. Pick provider and sink **profiles** when needed.
   {% endstep %}

{% step %}

### Enable Watcher and test

1. Enable **Watcher**.
2. Optional: enable **Autostart**.
3. Click the **red Save button**.
4. Play one title and confirm progress appears.
   {% endstep %}
   {% endstepper %}

### Scrobbler / Watcher quick guide

#### For most users

If you just want CrossWatch to scrobble playback, keep it simple:

1. Create a route from your media server to your tracker.
2. Set **Filters** so the route only matches the users or server you actually want.
3. Use **Options** if that route should behave differently from the global Watcher defaults.

In short:

* **Global settings** = default behavior for all routes
* **Route filters** = who or what a route should match
* **Route options** = route-specific overrides

#### For power users

Routes are where advanced setups happen.

Use separate routes when you want:

* different users scrobbled to different sink accounts
* different server profiles going to different tracker profiles
* route-specific auto-remove behavior
* Plex-specific custom ratings webhooks on one route but not another

How it works:

* Each route has its own provider, provider profile, sink, sink profile, filters, and options.
* Global Watcher settings still exist, but they are only the defaults.
* If a route needs special behavior, open that route’s **Options** and override the global default there.

A good example:

* one route for your own Plex account to Trakt
* one route for a family Jellyfin profile to SIMKL
* one route with auto-remove enabled
* another route with auto-remove disabled

#### Tips

* Avoid creating two identical routes.
* Be careful sending multiple providers to the same tracker profile at the same time. Most trackers do not handle simultaneous playback well.
* If you want one route to behave differently, change that route’s **Options** instead of changing the global defaults for everything.

### Global settings vs route settings

Global Watcher settings apply to all routes by default.

That is the best place for behavior you want everywhere.

Route **Filters** decide whether a route matches a playback event.

Without filters, a route can scrobble all playback it sees from that source/profile.

Route **Options** override the global defaults for that route only.

Most users can leave the global settings as they are.

Create routes first.

Set Filters early if you do not want the route to catch everything from that source/profile.

Change Options only when you actually need per-route behavior.

### Route fields (what they mean)

Routes usually include:

* **Enabled**: turns a route on/off.
* **Provider**: Plex, Emby, Jellyfin.
* **Provider profile**: `default` or `PLEX-P01`, etc.
* **Sink**: Trakt, SIMKL, MDBList.
* **Sink profile**: `default` or `TRAKT-P01`, etc.
* **Filters**: per-route scrobble restrictions.
* **Options**: per-route overrides for Watcher defaults.

{% hint style="info" %}
A route is identified by:

`provider + provider_instance + sink + sink_instance`

Avoid duplicates unless you want double-scrobbles.
{% endhint %}

### Common route options

* **Auto-remove from Watchlist**: removes completed movies from watchlists.
* **Ratings (Plex only)**: require ratings webhook helper added in Plex.

### Webhook tokens (helper endpoints)

Watcher itself does not need inbound webhooks.

One exception is **Plex ratings**, which uses:

* `POST /webhook/plexwatcher?uniqueID`

This URL token is generated by CrossWatch.

If you rotate tokens in **Settings → Scrobble → Watcher**, the old URL stops working.

Update the webhook URL in Plex after rotating.

### Filters

* **Username whitelist**: only scrobble listed users.
  * Supports `id:<accountID>` or `uuid:<accountUUID>`.
* **Server UUID** (Plex only): restrict to one server.
* **User UUID** (Emby/Jellyfin): restrict to one user.

### Migration from legacy Watcher config

Before **v0.9.0**, Watcher used `scrobble.watch.provider` + `scrobble.watch.sink`.

As of **v0.9.15**, CrossWatch does **not** auto-migrate legacy Watcher config anymore.

If you still have an old Watcher config, create routes yourself.

Starting with a clean `config.json` is usually the best path.

Use this checklist:

* [Migrate to routes mode](/crosswatch/scrobble/watcher/migrate-to-routes-mode.md)

### Advanced

<figure><img src="/files/CDx7qslfQ4bwGq0HrNig" alt=""><figcaption></figcaption></figure>

<details>

<summary>Playback event tuning</summary>

**Pause debounce (sec)** (default `5`)

* Ignores rapid duplicate pause events.
* Config: `scrobble.watch.pause_debounce_seconds`

**Suppress start @ (%)** (default `99`)

* Skips “start” events near the end of a title.
* Config: `scrobble.watch.suppress_start_at`

**Regress tolerance (%)** (default `5`)

* Clamps progress backwards jumps (seek glitches).
* Config: `scrobble.trakt.regress_tolerance_percent`

**Progress step (%)** (default `25`, range `1–25`)

* Minimum progress delta before CW sends an update to sinks (trackers).
* Higher values = fewer progress updates, thus fewer API calls.
* Lower values = frequent progress updates, higher API calls.
* Example: a 60 min movie.
  * `1%` ≈ 100 API calls
  * `5%` ≈ 20 API calls
  * `25%` ≈ 4 API calls

**Stop pause threshold (%)** (default `80`)

* Treat STOP below this threshold as PAUSE.
* Config: `scrobble.trakt.stop_pause_threshold`

**Force stop @ (%)** (default `80`)

* Treat STOP at/above this as completed.
* Config: `scrobble.trakt.force_stop_at`

</details>

### Troubleshooting quick checks

* **Nothing scrobbles**: confirm Watcher is enabled and you clicked Save.
* **Wrong user**: tighten **Username whitelist** / **User UUID** filters.
* **Wrong server (Plex)**: set **Server UUID**.
* **Missing matches**: add TMDb metadata: [Metadata](/crosswatch/providers/metadata.md)
* **Slow updates**: lower progress step, but expect more API calls.

### Related topics

* Scrobble overview: [Scrobble](/crosswatch/scrobble.md)
* Pick Watcher vs legacy webhooks: [Webhook or Watcher](/getting-started/first-time-setup/what-do-you-need/webhook-or-watcher.md)
* Improve matching quality: [Metadata](/crosswatch/providers/metadata.md)
* Legacy inbound endpoints: [Webhooks (legacy)](/crosswatch/scrobble/webhooks.md)

### Summary

Watcher scrobbles in real time.

Routes mode supports multiple sources and sinks.

Global settings are the defaults.

Use route filters so the route does not catch unwanted playback.

Use route options when one route needs different behavior.

Use profiles to scrobble multiple accounts or servers.

### Next steps

* Decide Watcher vs legacy webhook mode: [Webhook or Watcher](/getting-started/first-time-setup/what-do-you-need/webhook-or-watcher.md)
* Limit which libraries generate scrobbles: [Library Whitelisting](/crosswatch/library-whitelisting.md)
* Put CW behind TLS and a proxy (recommended for remote): [Reverse proxies](/related-information/reverse-proxies.md)
* Show “Now Playing” in the footer: [Playing Card](/crosswatch/scrobble/playing-card.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wiki.crosswatch.app/crosswatch/scrobble/watcher.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
