> 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/scheduling/scheduler-webhooks.md).

# Scheduler Webhooks

Scheduler Webhooks let CW notify an external service when a scheduled sync starts, succeeds or fails. Typical uses include:

1. Monitoring scheduled syncs with Healthchecks.io
2. Sending scheduled sync notifications through Notifiarr
3. Forwarding run information to an automation platform
4. Sending structured JSON to a custom monitoring service

Webhook delivery is best effort. A failed or unavailable webhook does not fail the sync itself.

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

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

### Supported scheduled runs

Scheduler Webhooks are sent for these run types:

1. Standard scheduled syncs
2. Advanced timed sync pair jobs
3. Steps inside recurring workflows

Scheduler Webhooks are not sent for:

1. Syncs started from the normal Sync button
2. Scheduler event triggers
3. Scheduled captures
4. Scheduled backups

The feature monitors scheduled sync execution, not every Scheduler activity.

### Available events

CW can send three events.

#### Start

Sent when a scheduled sync has been accepted and begins execution.

The JSON event is:

```json
{
  "event": "start",
  "status": "started"
}
```

#### Success

Sent when the sync completes with exit code `0`.

The JSON event is:

```json
{
  "event": "success",
  "status": "ok",
  "exit_code": 0
}
```

#### Failure

Sent when the sync completes with a nonzero exit code, does not produce a valid exit code, or the Scheduler cannot start an expected sync.

The JSON event is:

```json
{
  "event": "failure",
  "status": "failed",
  "exit_code": 1
}
```

### Basic setup

1. Open `Settings`
2. Open `Scheduling`
3. Find `Scheduler Webhooks`
4. Enable Scheduler Webhooks
5. Select the payload format
6. Enter the appropriate URL
7. Keep the timeout at `10` seconds unless the receiver requires more time
8. Save the Scheduler configuration

CW accepts only complete `http://` or `https://` URLs.

### Which URL field should I use

#### Compatible base URL

Use this for Healthchecks.io and other monitoring services that use the same URL convention.

CW derives the event URLs automatically:

```
Start:   <base URL>/start
Success: <base URL>
Failure: <base URL>/fail
```

For Healthchecks.io, this is normally the correct field.

Example base URL:

```
https://hc-ping.com/YOUR_CHECK_UUID
```

Do not paste this URL into `All events URL`. When `All events URL` is configured, all events are sent to exactly that URL and the compatible base URL is ignored.

#### All events URL

Use this when one webhook endpoint receives all events and uses the JSON `event` or `status` field to determine what happened.

Example:

```
https://hooks.example.com/crosswatch
```

CW sends start, success and failure events to the same URL.

Use this for:

1. Custom webhook receivers
2. Automation platforms with one incoming webhook
3. Logging or monitoring APIs that process JSON payloads
4. Services where the event type is determined from the request body

#### Start callback URL

Use this when the start event must be sent to a separate URL.

Example:

```
https://hooks.example.com/crosswatch/start
```

#### Success callback URL

Use this when successful runs must be sent to a separate URL.

Example:

```
https://hooks.example.com/crosswatch/success
```

#### Failure callback URL

Use this when failed runs must be sent to a separate URL.

Example:

```
https://hooks.example.com/crosswatch/failure
```

The event specific callback URLs override the other URL options for their event.

### URL priority

For the `CW JSON` payload format, CW selects a URL in this order:

1. The callback URL for the specific event
2. All events URL
3. Compatible base URL

Example:

```
All events URL:        https://hooks.example.com/crosswatch
Failure callback URL:  https://alerts.example.com/crosswatch-failed
```

In this configuration:

```
Start goes to:   https://hooks.example.com/crosswatch
Success goes to: https://hooks.example.com/crosswatch
Failure goes to: https://alerts.example.com/crosswatch-failed
```

### Healthchecks.io setup

Healthchecks.io expects a base ping URL and supports `/start` and `/fail` suffixes.

Configure CW as follows:

1. Create or open a check in Healthchecks.io
2. Copy its Ping URL
3. Select `CW JSON`
4. Paste the Ping URL into `Compatible base URL`
5. Leave `All events URL` empty
6. Leave the three callback URL fields empty
7. Enable Scheduler Webhooks
8. Save the Scheduler configuration

Example:

```
Compatible base URL:
https://hc-ping.com/12345678-1234-1234-1234-123456789012
```

CW sends:

```
Start:
https://hc-ping.com/12345678-1234-1234-1234-123456789012/start

Success:
https://hc-ping.com/12345678-1234-1234-1234-123456789012

Failure:
https://hc-ping.com/12345678-1234-1234-1234-123456789012/fail
```

Query parameters are preserved when CW adds `/start` or `/fail`.

Example:

```
Base:
https://monitor.example/ping/crosswatch?source=crosswatch

Start:
https://monitor.example/ping/crosswatch/start?source=crosswatch

Failure:
https://monitor.example/ping/crosswatch/fail?source=crosswatch
```

CW does not expand variables inside URLs and does not automatically add the CW run ID as a query parameter.

Treat the complete Ping URL as a secret. Anyone with the URL can send events to the check.

### Notifiarr setup

Notifiarr requires its own Passthrough payload format.

Configure CW as follows:

1. Select `Notifiarr Passthrough`
2. Paste the Notifiarr Passthrough URL into `Notifiarr passthrough URL`
3. Optionally enter a numeric Discord channel ID
4. Enable Scheduler Webhooks
5. Save the Scheduler configuration

Example endpoint:

```
https://notifiarr.com/api/v1/notification/passthrough/YOUR_API_KEY
```

Start, success and failure notifications are all sent to this same URL.

CW automatically generates the Notifiarr notification and Discord payload. It includes the run type, sync pair, workflow information, duration, exit code and available summary counters.

The channel ID is optional. Enter only the numeric Discord channel ID, without spaces or other characters.

Treat the Passthrough URL as a secret because it contains the Notifiarr API key.

### Timeout

The timeout controls how long CW waits for the webhook receiver.

```
Default: 10 seconds
Minimum: 1 second
Maximum: 60 seconds
```

A timeout only affects the webhook request. It does not change the sync result.
{% endtab %}

{% tab title="Power users" %}

### Request behavior

CW sends webhooks using:

```
Method: POST
Content type: application/json
User agent: CW scheduler webhook
```

Each event is sent once.

CW does not currently provide:

1. Automatic retries
2. Configurable HTTP headers
3. Header based authentication
4. Payload templates
5. URL variable substitution
6. Webhook signing
7. Separate timeout values per event

A final HTTP `4xx` or `5xx` response is treated as a failed delivery.

Redirect handling follows the default HTTP client behavior.

Webhook delivery remains independent from the sync result.

### CW JSON payload

A completed workflow step can produce a payload similar to this:

```json
{
  "event": "success",
  "status": "ok",
  "run_id": "7ab2bb3e1b7a4d24",
  "scheduler_mode": "advanced_workflow",
  "workflow_id": "nightly-sync",
  "workflow_step_id": "plex-to-trakt",
  "pair_id": "plex-trakt",
  "started_at": "2026-08-03T18:00:00Z",
  "finished_at": "2026-08-03T18:01:14Z",
  "duration_sec": 74.2,
  "exit_code": 0,
  "summary": {
    "added": 12,
    "removed": 1,
    "updated": 3,
    "unresolved": 2
  }
}
```

Empty values are omitted.

The possible top level fields are:

```
event
status
run_id
scheduler_mode
job_id
workflow_id
workflow_step_id
pair_id
started_at
finished_at
duration_sec
exit_code
summary
```

The `summary` object can contain:

```
added
removed
updated
unresolved
errors
blocked
skipped
```

Zero value counters are normally omitted.

### Scheduler context values

The webhook service accepts these scheduler modes:

```
standard
advanced
advanced_workflow
```

Other Scheduler modes do not generate Scheduler Webhooks.

#### Standard schedule

```json
{
  "source": "scheduler",
  "scheduler_mode": "standard"
}
```

#### Advanced timed sync pair

```json
{
  "source": "scheduler",
  "scheduler_mode": "advanced",
  "job_id": "job-id",
  "pair_id": "pair-id"
}
```

#### Recurring workflow step

```json
{
  "source": "scheduler",
  "scheduler_mode": "advanced_workflow",
  "workflow_id": "workflow-id",
  "workflow_step_id": "step-id",
  "pair_id": "pair-id"
}
```

### Configuration structure

Scheduler Webhooks are stored inside the `scheduling` configuration.

```json
{
  "scheduling": {
    "webhooks": {
      "enabled": false,
      "url": "",
      "base_url": "",
      "start_url": "",
      "success_url": "",
      "failure_url": "",
      "payload_format": "crosswatch",
      "notifiarr_channel_id": "",
      "timeout_seconds": 10
    }
  }
}
```

#### Configuration fields

`enabled`

Enables or disables Scheduler Webhook delivery.

`url`

The common URL for all events.

With `payload_format` set to `notifiarr`, this becomes the required Notifiarr Passthrough URL.

`base_url`

A Healthchecks.io compatible base URL.

CW adds `/start` and `/fail` when required.

`start_url`

Optional explicit destination for start events.

`success_url`

Optional explicit destination for successful completion events.

`failure_url`

Optional explicit destination for failed completion events.

`payload_format`

Supported values:

```
crosswatch
notifiarr
```

The legacy value `notifiarr_passthrough` is normalized to `notifiarr`.

`notifiarr_channel_id`

Optional numeric Discord channel ID added to the Notifiarr payload.

`timeout_seconds`

Webhook request timeout between `1` and `60` seconds.

Values below or above this range are limited to the supported range.

### Healthchecks.io configuration example

```json
{
  "scheduling": {
    "webhooks": {
      "enabled": true,
      "url": "",
      "base_url": "https://hc-ping.com/YOUR_CHECK_UUID",
      "start_url": "",
      "success_url": "",
      "failure_url": "",
      "payload_format": "crosswatch",
      "notifiarr_channel_id": "",
      "timeout_seconds": 10
    }
  }
}
```

### Generic single endpoint configuration example

```json
{
  "scheduling": {
    "webhooks": {
      "enabled": true,
      "url": "https://hooks.example.com/crosswatch",
      "base_url": "",
      "start_url": "",
      "success_url": "",
      "failure_url": "",
      "payload_format": "crosswatch",
      "notifiarr_channel_id": "",
      "timeout_seconds": 10
    }
  }
}
```

### Separate callback configuration example

```json
{
  "scheduling": {
    "webhooks": {
      "enabled": true,
      "url": "",
      "base_url": "",
      "start_url": "https://hooks.example.com/crosswatch/start",
      "success_url": "https://hooks.example.com/crosswatch/success",
      "failure_url": "https://hooks.example.com/crosswatch/failure",
      "payload_format": "crosswatch",
      "notifiarr_channel_id": "",
      "timeout_seconds": 10
    }
  }
}
```

### Notifiarr configuration example

```json
{
  "scheduling": {
    "webhooks": {
      "enabled": true,
      "url": "https://notifiarr.com/api/v1/notification/passthrough/YOUR_API_KEY",
      "base_url": "",
      "start_url": "",
      "success_url": "",
      "failure_url": "",
      "payload_format": "notifiarr",
      "notifiarr_channel_id": "123456789012345678",
      "timeout_seconds": 10
    }
  }
}
```

When Notifiarr mode is selected, CW always uses `url` for all three events. The compatible base URL and event specific URLs are ignored.

### Notifiarr event presentation

CW uses different notification colours for each event:

```
Start:   4F8CFF
Success: 2ECC71
Failure: FF5A5F
```

The generated Notifiarr payload contains:

```
notification.name
notification.event
discord.color
discord.ping
discord.images
discord.text
discord.ids.channel
```

The `discord.ids.channel` object is only added when a channel ID is configured.

Notification fields are limited to the first `25` fields.

### Security

All Scheduler Webhook URL fields are treated as sensitive configuration values.

CW:

1. Encrypts these URL values when saving the configuration
2. Masks them in redacted configuration responses
3. Preserves the existing secret when a masked value is submitted again
4. Does not include the callback URL in Scheduler status logs
5. Logs only the event and delivery result

The channel ID and timeout are not secrets.

### Logging

Successful delivery produces a log similar to:

```
[i] Scheduler webhook start: delivered
[i] Scheduler webhook success: delivered
```

Failed delivery produces a log similar to:

```
[!] Scheduler webhook failure: ReadTimeout
[!] Scheduler webhook failure: ConnectionError
[!] Scheduler webhook failure: HTTPError
```

Only the exception type is logged. The secret URL and response body are not logged.

`delivered` means the configured receiver accepted the HTTP request. It does not guarantee that a downstream service displayed or processed the notification.

### Troubleshooting

#### Healthchecks.io reports every event as successful

The Ping URL was probably entered in `All events URL`.

Move it to `Compatible base URL` and clear `All events URL`.

#### Healthchecks.io receives no events

Confirm:

1. Scheduler Webhooks are enabled
2. The URL is in `Compatible base URL`
3. The URL starts with `http://` or `https://`
4. The run is a standard schedule, advanced timed sync or recurring workflow step
5. The Scheduler logs contain a delivery result

#### The custom receiver cannot determine the event

Use the `event` or `status` value in the JSON body.

Possible values are:

```
event: start
status: started

event: success
status: ok

event: failure
status: failed
```

#### Notifiarr rejects the request

Confirm:

1. Payload format is `Notifiarr Passthrough`
2. The URL is the Notifiarr Passthrough endpoint
3. The complete API key is present in the URL
4. The optional channel ID contains only numbers
5. The timeout is long enough for the Notifiarr request

#### Webhook delivery fails but the sync succeeds

This is expected. Scheduler Webhooks use best effort delivery and do not control the sync exit code.

#### Webhooks are enabled but no URL is configured

The UI prevents saving an enabled webhook configuration without an appropriate URL.

A directly edited configuration with no usable URL results in no callback being sent.

### Scheduling API warning

The Scheduler configuration is available through:

```
GET /api/scheduling
POST /api/scheduling
```

The POST endpoint replaces the complete `scheduling` object. Sending only a `webhooks` object can remove the existing standard and advanced Scheduler configuration.

Read the existing scheduling configuration first, update its `webhooks` block, then submit the complete scheduling object.
{% 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 dynamically by asking a question.

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

```
GET https://wiki.crosswatch.app/crosswatch/settings/scheduling/scheduler-webhooks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
