> For the complete documentation index, see [llms.txt](https://wiki.crosswatch.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.crosswatch.app/crosswatch/settings/scrobbler/webhooks.md).

# Webhooks

Configure webhook-based scrobbling from Plex, Jellyfin, or Emby to supported sinks.

Receive playback events from Plex, Jellyfin or Emby and forward them to connected trackers, media servers or CrossWatch.

The media server sends an HTTP request to a URL generated by CrossWatch. CrossWatch identifies the source profile, checks its filters, resolves the media and sends the accepted event to its configured destinations.

Webhooks handle new playback activity and supported Plex rating changes. Use regular sync pairs to reconcile existing history, watchlists, ratings or other stored data.

{% tabs %}
{% tab title="End users" %}

### Before you start

1. Connect the source media server profile in **Settings → Connections**.
2. Connect each destination profile you want to update.
3. Make sure your media server can send webhook notifications.
4. Confirm the server can reach the CrossWatch address used in the generated URL.
5. Decide which playback users and libraries should be included.

Only configured profiles appear in the selectors. CrossWatch may also query the source server to fill in missing metadata, library information or playback progress, so keep that connection working.

#### Supported sources and destinations

| Role                                 | Providers                                                                          |
| ------------------------------------ | ---------------------------------------------------------------------------------- |
| Webhook sources                      | Plex, Jellyfin, Emby.                                                              |
| Media server and client destinations | Plex, Jellyfin, Emby, Kodi.                                                        |
| Other destinations                   | CrossWatch, Trakt, SIMKL, MDBList, Floppy, PunchPlay, BingeBase, FlickList, Scrob. |

A destination's capabilities determine whether it records playback progress, current activity, completed history or a combination. Receiving the same event does not mean every destination exposes the same information.

Kodi and Scrob can receive scrobbles through this workflow, but cannot act as incoming webhook sources here.

A source and destination may use the same provider with different profiles. CrossWatch rejects a mapping back to the exact same provider profile.

### Add a webhook

1. Open **Settings → Scrobbler**.
2. Select **Add webhook**.
3. On **Source**, select the media server and source profile.
4. Select the destination provider and profile.
5. Review **Filters** and **Options**.
6. For Plex, configure **Ratings** if required.
7. Select **Save changes**.
8. Copy the generated endpoint URL.
9. Add that URL to the source server's webhook configuration.
10. Play a movie or episode and check delivery in CrossWatch and the destination.

The URL appears after saving. The source provider and profile are fixed when editing an existing mapping. Create a replacement mapping if you need a different source.

Webhooks do not require the Watcher runtime to be started. CrossWatch must be running, the webhook source profile must be enabled, and the source server must send events.

### One source profile, one URL

CrossWatch shows one card for each destination, but every card using the same source provider profile shares one inbound URL.

For example:

<table><thead><tr><th>Source profile</th><th>Destination profile</th><th width="238.6614990234375">Incoming URL</th></tr></thead><tbody><tr><td>Plex Home</td><td>Trakt Personal</td><td>The Plex Home URL.</td></tr><tr><td>Plex Home</td><td>SIMKL Personal</td><td>The same Plex Home URL.</td></tr><tr><td>Plex Home</td><td>Jellyfin Personal</td><td>The same Plex Home URL.</td></tr></tbody></table>

Add that URL to Plex once. CrossWatch distributes each accepted event to the configured destinations.

To add another destination, select **Add webhook** again and reuse the source profile. The current webhook model stores one destination profile per destination provider for a given source profile. It does not let you add two separate cards from that source to two profiles of the same destination provider.

#### Shared and separate settings

| Setting                                               | Scope                                                     |
| ----------------------------------------------------- | --------------------------------------------------------- |
| Endpoint URL and token                                | Shared by the source profile.                             |
| Enabled state                                         | Shared by the source profile.                             |
| Username and media filters                            | Shared by the source profile.                             |
| Pause debounce and suppress start overrides           | Shared by the source profile.                             |
| Plex rating switches                                  | Shared by the source profile.                             |
| Destination provider and selected destination profile | Specific to the destination card.                         |
| Anime ID Mapping                                      | Separate setting for the CrossWatch or SIMKL destination. |
| Track rewatches                                       | Setting for the SIMKL destination.                        |

{% hint style="info" %}
Changing filters or timing options from one card affects every destination attached to that source profile. Disabling one card disables webhook processing for the entire source profile. Deleting a card removes only its destination.
{% endhint %}

### Configure the media server

#### Plex

Use Plex's native webhook configuration for the account that generates the playback events. Your Plex account must have access to the native Webhooks feature.

1. Open the Plex account settings and its **Webhooks** section.
2. Add the full Plex profile URL copied from CrossWatch.
3. Save the webhook.
4. Play, pause, resume and stop a movie or episode from the intended account.
5. If you enabled Plex ratings in CrossWatch, change a rating and verify that separately.

Plex sends its native payload, normally as multipart form data containing a JSON `payload` field. No custom JSON template is required.

CrossWatch accepts these Plex events:

| Plex event       | Purpose                                                                        |
| ---------------- | ------------------------------------------------------------------------------ |
| `media.play`     | Start playback.                                                                |
| `media.resume`   | Resume playback.                                                               |
| `media.pause`    | Pause playback.                                                                |
| `media.stop`     | Stop playback, with completion decided from the available progress and policy. |
| `media.scrobble` | Plex reports the item as watched.                                              |
| `media.rate`     | Forward a rating change when rating destinations are enabled.                  |

Playback scrobbling supports movies and episodes. Rating handling also accepts series, subject to destination support. Other Plex notifications are ignored by this scrobbling path.

For shared servers, check the playback account and Plex server UUID. A working server connection does not mean every received account event belongs to the destination account you selected.

#### Jellyfin

Use the [official Jellyfin Webhook plugin](https://github.com/jellyfin/jellyfin-plugin-webhook).

1. Install the plugin and restart Jellyfin if requested.
2. Open the plugin configuration and select **Add Generic Destination**.
3. Enter a name and paste the full Jellyfin profile URL from CrossWatch.
4. Enable the destination.
5. Select **Playback Start**, **Playback Progress** and **Playback Stop**.
6. Select the intended users and enable **Movies** and **Episodes**.
7. Enable **Send All Properties (ignores template)**.
8. If configuring headers, use `Content-Type: application/json`.
9. Save and test playback, including a pause and resume.

This sends the plugin's event properties without requiring a custom template. CrossWatch accepts both the plugin's flat properties and supported nested item data.

**Playback Progress** matters because the plugin reports pause state through progress notifications. CrossWatch can derive pause and resume transitions from `IsPaused`. Ordinary progress notifications may update Currently Watching without producing a new outbound scrobble every time.

A notification template containing only a human readable message does not provide the structured playback data required by CrossWatch.

#### Emby

Configure an Emby webhook notification that sends playback data to the full Emby profile URL generated by CrossWatch. Emby's native webhook feature requires Emby Premiere, as described in the [Emby documentation](https://emby.media/support/articles/Webhooks.html).

1. Open Emby's webhook or notification configuration.
2. Add the CrossWatch URL as the destination.
3. Enable playback start, pause, unpause or resume, and stop notifications available in your Emby setup.
4. Include the intended users and movie or episode activity.
5. Keep the structured event and item data in the payload.
6. Save and test playback.

CrossWatch normalizes event names such as `playback.start`, `playback.pause`, `playback.unpause` and `playback.stop`. It also accepts supported stop or scrobble variants.

Generic Emby progress notifications are not handled like Jellyfin's Playback Progress events. Ensure the start, pause, unpause and stop events are sent.

### Filters

Open **Filters** on a webhook card. These filters apply to all destinations using the source profile.

#### Username whitelist

Enter one username per line, or use **Find users** to select the intended media account.

With an ordinary unassigned webhook, an empty whitelist does not restrict users. If the webhook is assigned to a CrossWatch user profile, an empty username whitelist blocks playback events. Set an explicit account filter for an assigned webhook.

Use the name reported by the media server. For Jellyfin and Emby, retain its exact spelling and case. Plex also supports account identifiers, described in the Power users tab.

#### Plex server filters

| Filter                    | Behavior                                                                 |
| ------------------------- | ------------------------------------------------------------------------ |
| **Server UUID allowlist** | Accept only listed servers. An event without a matching UUID is ignored. |
| **Server UUID blocklist** | Ignore listed servers, even when they also appear in the allowlist.      |

Use **Fetch server UUID** to retrieve the selected Plex server's identity. Server names and UUIDs are different values.

#### Media exclusions

| Filter                                   | Behavior                                                                            | Example                      |
| ---------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------- |
| **Ignored path prefixes**                | Skip media at or below a matching directory path.                                   | `/media/placeholders`        |
| **Ignored filename patterns**            | Skip paths or filenames containing the supplied text.                               | `{edition-Trailer}`          |
| **Ignored editions**                     | Skip an exact edition name, ignoring case.                                          | `Trailer`                    |
| **Ignore Agregarr placeholder trailers** | Check Trailer editions, the trailer filename marker and `.comingsoon` marker files. | Placeholder trailer content. |

Filename patterns are text matches, not regular expressions or wildcard expressions. Path and filename comparisons ignore case and normalize path separators.

These exclusions depend on metadata available in the incoming event. A `.comingsoon` check also requires CrossWatch to access the referenced directory. A path on a remote media server is not automatically available inside the CrossWatch container.

#### Library restrictions

Scrobbling library selections on the source connection still apply. A webhook can pass its username and server filters and still be excluded by the source's library configuration.

If events are ignored unexpectedly, check both the webhook filters and the source connection's scrobbling library selection.

### Options and defaults

The **Webhook defaults** section on the Scrobbler page supplies global timing values. On a webhook card, **Options → Override profile options** lets you save values for that source profile.

| Option             | Unit                | Default | Behavior                                                            |
| ------------------ | ------------------- | ------- | ------------------------------------------------------------------- |
| **Pause debounce** | Seconds             | 5       | Suppresses repeated pause events arriving within the interval.      |
| **Suppress start** | Playback percentage | 99      | Ignores start or resume events at or above the configured progress. |

Suppress start is a percentage, even though it appears beside a setting measured in seconds. A value of 99 prevents a nearly finished item from reappearing as newly playing because of a late start event.

Leave inherited values in place unless you need different behavior for that source. Timing settings are shared by all its destination cards.

#### Completion behavior

A stop event does not automatically mean a completed watch. CrossWatch uses playback progress, previous session information and supported server completion signals before recording completion.

The shared scrobble policy defaults to a **Watched threshold** of 90% and **Final stop trust** of 95%. The webhook Options tab exposes pause debounce and suppress start, rather than separate per destination completion overrides. Provider completion rules still apply.

Plex's `media.scrobble` is an explicit watched signal. Jellyfin can also use supported played flags in its stop payload. CrossWatch includes protections for duplicated completion events, small progress regressions and misleading final progress changes.

If **Auto remove from Watchlists** is enabled and the completion qualifies, CrossWatch can remove the item from applicable watchlists. This can affect more than the destination shown on the webhook card. It removes watchlist entries, not media files.

#### Anime ID Mapping

This option is available for CrossWatch and SIMKL destinations.

Enable global **Anime ID Mapping** in **Connections → Metadata** first, then enable it on the applicable webhook destination. CrossWatch uses the local anime mapping data to enrich the item before delivery.

The option is separate for CrossWatch and SIMKL. It does not enable anime mapping for every destination attached to the source.

#### **Update all copies**

When enabled, CrossWatch updates all matching copies of the same media item on Plex, Jellyfin, Emby and Kodi scrobble destinations, instead of updating only the first match found. This option is enabled by default.

#### **PlexKodiConnect compatibility**

When enabled, CrossWatch recognizes Kodi playback that originates from PlexKodiConnect and resolves it as Plex backed media. This improves ID matching and prevents playback from being missed or incorrectly matched during scrobbling.

#### Track rewatches

The SIMKL destination offers **Track rewatches**. CrossWatch requires a qualifying SIMKL Pro or VIP account for this option and checks the subscription when enabling it.

The integration applies a 48 hour gap per movie or episode. The configured watched threshold still applies, with a minimum of 80% for this rewatch behavior.

A new playback session and a repeated notification for the same completed session are handled differently. Enabling rewatches does not make duplicate webhook notifications count as new watches.

### Plex ratings

The **Ratings** tab appears for Plex webhook sources. It forwards `media.rate` events independently of playback start and stop handling.

Current rating destinations are **CrossWatch, Trakt, SIMKL, MDBList, Floppy, PunchPlay, FlickList and Scrob**, where configured and supported.

1. Add the required provider as a destination for the Plex source profile.
2. Open **Ratings** on its webhook card.
3. Enable the intended rating destinations.
4. Save changes.
5. Change a Plex rating and check the destination.

The rating switches are shared by every webhook card using that Plex source profile. A checked switch alone is insufficient, the provider must also be an active, configured destination for that source.

#### Rating support varies

<table><thead><tr><th>Destination</th><th width="352.994873046875">Handling in this webhook path</th></tr></thead><tbody><tr><td>Trakt</td><td>Movie, series and episode ratings, including rating removal.</td></tr><tr><td>SIMKL and MDBList</td><td>Movie and series ratings. Episode ratings are not forwarded through these branches.</td></tr><tr><td>CrossWatch, Floppy, PunchPlay, FlickList and Scrob</td><td>Uses each provider's rating adapter and supported media types.</td></tr></tbody></table>

Plex rating removal is processed as a cleared rating where the destination supports it. Do not assume that clearing a rating has identical behavior across providers.

#### Webhook ratings versus Watcher ratings

CrossWatch also provides global and custom route Plex ratings URLs for Watcher. Those URLs use `/webhook/plexwatcher` and are intended for rating events.

Use the normal Plex profile URL for webhook playback and its enabled ratings. Use a Watcher ratings URL when playback is handled by Watcher and you need incoming rating changes.

If both mechanisms deliver the same rating to the same account, you can create overlapping writes. Check the global ratings destinations, custom Watcher route settings and profile webhook rating switches together.

### Media server destinations

Plex, Jellyfin, Emby and Kodi destinations can update supported resume progress and watched status for matching items in their libraries.

They do not copy media files or remotely start the destination player. The target library must contain the matching movie or episode, and its connection must have the required access.

Destination adapters can skip an item that is already watched, has no useful progress, or cannot be resolved. They can also refuse to overwrite an active destination playback session. Check the destination result when an update does not appear.

### Enable, disable, delete or regenerate

| Action                       | Effect                                                                                                        |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Disable a card               | Disables webhook processing for the entire source profile. Its URL remains available, but events are ignored. |
| Enable a card                | Reactivates the source profile and its configured destinations.                                               |
| Delete a card                | Removes that destination from the source profile.                                                             |
| Delete the final destination | Removes the profile webhook and its endpoint token.                                                           |
| Regenerate the URL           | Replaces the source profile's token and invalidates the old URL.                                              |

URL regeneration requires confirmation and affects every card sharing that source profile. After regeneration, copy the new URL, replace it in the media server and test a fresh event.

For a removed destination, delete the card instead of using Disable when other destinations should continue receiving events.

#### Legacy URL cleanup

Older installations may still have tokens for `/webhook/plextrakt`, `/webhook/jellyfintrakt` or `/webhook/embytrakt`.

Move the media server to its generated profile URL before using the legacy cleanup action on the Scrobbler page. Cleanup removes the old media webhook tokens. The separate Plex Watcher ratings endpoint is not the legacy playback endpoint.

### Check that it works

1. Confirm the webhook card is active and both connections are configured.
2. Start a known movie or episode using a permitted account.
3. Pause and resume playback.
4. Finish or stop the item and inspect the resulting progress or history.
5. Check each destination separately.
6. Test a Plex rating separately if enabled.

The card's active status describes configuration readiness. It does not prove that the media server has delivered a request or that every destination accepted it.

Use CrossWatch's scrobbler logs and activity information to distinguish receiving an event, filtering it and delivering it. Debug logging adds details such as the event type, content type, resolved IDs and suppression reason.

{% hint style="info" %}
An HTTP 200 response is not proof of a successful scrobble. CrossWatch also acknowledges many ignored, suppressed or malformed notifications with HTTP 200. Check the processing result, logs and destination data.
{% endhint %}

### Troubleshooting

| Symptom                             | What to check                                                                                                    |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Needs setup**                     | Source connection, destination credentials and selected profiles.                                                |
| No incoming events                  | Full URL, source notification settings, network reachability, port and certificate trust.                        |
| URL points to the wrong host        | Open CrossWatch through the address the server should use, then copy the URL again. Check proxy headers.         |
| Invalid profile after regeneration  | Replace the old URL in the media server. Keep the complete query string.                                         |
| Events arrive but are ignored       | Username, Plex UUID, source library selection, media type, media exclusions and enabled state.                   |
| Assigned webhook ignores all users  | Add an explicit username whitelist for the source profile.                                                       |
| Jellyfin pause or resume is missing | Enable Playback Progress and include all properties, particularly pause state.                                   |
| Payload parse errors                | Send structured JSON or the provider's supported native payload, not a notification sentence or HTML page.       |
| Item cannot be matched              | Check external IDs and episode coordinates, and confirm the item exists at the destination.                      |
| Progress is not updated             | Check destination capabilities, already watched state, missing duration and active destination playback.         |
| Rating is ignored                   | Check the Plex Ratings switches, destination membership, authentication and supported media type.                |
| Duplicate scrobbles                 | Check duplicate server URLs, Watcher routes and other integrations writing to the same destination.              |
| Completion is missing or premature  | Check source progress, completion events and shared watched policy. A generic notification test is insufficient. |
| One destination fails               | Inspect its result and credentials independently of other destination cards.                                     |

Do not use `localhost` in the media server configuration unless CrossWatch is reachable in that same network namespace. In a container, `localhost` normally refers to the container itself.
{% endtab %}

{% tab title="Power users" %}

### Endpoint and request model

Generated endpoints use a source profile token:

```
/webhook/plex?profile=<generated-token>
/webhook/jellyfin?profile=<generated-token>
/webhook/emby?profile=<generated-token>
```

Copy the complete URL from Scrobbler. The value of `profile` is a secret token, not the visible profile name or a destination identifier.

The token resolves the source provider instance and its effective webhook settings. CrossWatch then checks the source's enabled state, connection and destinations before processing the payload.

#### Accepted request formats

| Source   | Formats                                                                                                   |
| -------- | --------------------------------------------------------------------------------------------------------- |
| Plex     | Multipart form with a JSON `payload` field, URL encoded form with a JSON `payload` field, or a JSON body. |
| Jellyfin | A JSON body, or a URL encoded form containing JSON under `payload`, `data` or `json`.                     |
| Emby     | A JSON body, or a URL encoded form containing JSON under `payload`, `data` or `json`.                     |

Use HTTP **POST**. Opening the URL in a browser sends a GET request and does not test playback handling.

A useful payload carries the event type, media type, account identity, item IDs, progress or position and runtime, and a session identifier. Episodes also need the series identity and season and episode coordinates.

CrossWatch uses server queries and metadata resolution where available, but missing information can still prevent matching, account filtering or completion handling. Preserve the source's structured payload rather than reducing it to a title and percentage.

#### Event normalization

| Source   | Start or resume                                                              | Pause                                                             | Stop or completion                                                            |
| -------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Plex     | `media.play`, `media.resume`                                                 | `media.pause`                                                     | `media.stop`, `media.scrobble`                                                |
| Jellyfin | `PlaybackStart`, `PlaybackStarted`, `PlaybackResume`, supported play aliases | `PlaybackPause`, `PlaybackPaused`, or a progress pause transition | `PlaybackStop`, `PlaybackStopped`, supported stop or scrobble aliases         |
| Emby     | `playbackstart`, `playbackunpause`, supported play or resume aliases         | `playbackpause`, supported pause aliases                          | `playbackstop`, `playbackstopped`, `playbackscrobble`, supported stop aliases |

Jellyfin's `PlaybackProgress` has additional transition and current activity handling. Emby removes dots and underscores from its event name before matching. Plex ratings use a separate `media.rate` branch.

Supported playback media are movies and episodes. A whole season, music track, library update or login notification is not a playback scrobble in this workflow.

### Configuration scope

Effective webhook settings merge global defaults, provider defaults and source profile overrides.

Profile configuration stores the destination provider list plus one selected instance for each destination provider. This is why different cards can share a URL and filters while using different destinations, and why one source cannot have multiple instances of the same destination provider through this model.

Anime mapping flags are stored separately for CrossWatch and SIMKL. The SIMKL rewatch flag is applied only when dispatching to SIMKL.

The global `scrobble.trakt` block contains shared progress and completion policy used by the webhook handlers despite its historical name. Changing that policy can affect webhook behavior beyond Trakt. Watcher route overrides should not be assumed to apply to profile webhooks.

#### Account scope

Plex username matching normalizes case and punctuation. Its whitelist can also contain `id:<account-id>` or `uuid:<account-uuid>` entries.

Jellyfin and Emby compare reported account names against their configured lists. Use the exact server account name. A webhook assigned to a CrossWatch user profile requires an explicit whitelist, so the profile token alone is not a substitute for selecting the playback user.

Use separate source connection profiles when users need different filters or different accounts at the same destination provider. Review each resulting URL and account filter independently.

### Completion replay and failed delivery

CrossWatch records completion state using available provider, account, media and playback session identity. Repeated successful completion notifications for the same identified session are suppressed. A distinct session can represent a legitimate later watch.

Failed completion delivery remains eligible for processing when the completion event is received again. This does not provide a durable webhook queue or guarantee automatic background retry. The profile webhook dispatcher calls destinations directly, whereas Watcher has a separate pending delivery mechanism.

If the source does not resend a failed event, inspect the destination and use a regular sync or an Editor operation to reconcile missing data. Do not rely on the incoming HTTP acknowledgement as a delivery guarantee.

Duplicate suppression also does not make overlapping Watcher and webhook routes safe by definition. Configure a single playback path for each source and destination combination.

### Networking and token handling

The source server must be able to reach the webhook URL. Public internet exposure is unnecessary when both applications communicate over a reachable private network.

A reverse proxy must preserve the POST body, content type, path and query string. An interactive sign in page in front of the webhook endpoint prevents normal media server delivery. Keep the token protected and use HTTPS when requests cross an untrusted network.

CrossWatch builds the displayed URL from the request's base address and recognizes a forwarded HTTPS scheme. Verify the hostname and protocol when accessing it through a proxy.

Regenerating a profile token invalidates the old value for every destination on that source. Legacy token authentication is a separate compatibility path. An invalid profile token can produce an ignored response, while invalid legacy token authentication uses a dedicated authentication error path.

### Management API reference

These routes are relative to `/api/scrobbler` and use the application's access controls:

| Method and route                    | Purpose                                                                                  |
| ----------------------------------- | ---------------------------------------------------------------------------------------- |
| `POST /webhooks/profile`            | Save a source profile's webhook settings and destination selection.                      |
| `POST /webhooks/profile/disable`    | Disable the source profile, or remove a destination with the applicable removal payload. |
| `POST /webhooks/profile/regenerate` | Replace the source profile's endpoint token.                                             |
| `POST /webhooks/cleanup-legacy`     | Remove legacy media webhook tokens.                                                      |

The inbound `/webhook/...` routes and these management routes serve different purposes. A generated inbound URL is for the media server to deliver notifications.
{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

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

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://wiki.crosswatch.app/crosswatch/settings/scrobbler/webhooks.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
