The request#
Each event is sent to each webhook that subscribes to it as one HTTPS request.
| Part | Value |
|---|
| Method | POST to the webhook's address, exactly as entered, query string included |
Content-Type | application/json; charset=utf-8 |
User-Agent | Renable-Monitors/1 |
Idempotency-Key | The delivery's id. The same on every retry of one delivery, different for each webhook the event goes to |
X-Renable-Delivery | The delivery's id, the same value as Idempotency-Key |
X-Renable-Event | The event type: monitor.matched, monitor.triggered or monitor.cleared |
| Body | The JSON message described below |
Answer with any 2xx status to accept the message. The body of your answer is not read for meaning; its first 2 KB is kept and shown on the monitor's Activity tab, which makes a short, readable answer — a task id, an error message — useful to whoever looks there.
The message#
| Field | Type | What it holds |
|---|
type | string | The event: monitor.matched, monitor.triggered or monitor.cleared (see below) |
version | number | 1 |
test | boolean | true for a message sent with Send test, false otherwise |
id | string | The event's id. Every webhook the event goes to receives the same id; a test carries a fresh one |
occurredAt | string | When the event happened, as an ISO 8601 UTC timestamp (2026-09-24T12:00:00.000Z) |
monitor | object | The monitor: id, name, link |
organization | object | Your organization: id (a number), name |
project | object or null | The project the catalog or channel belongs to: id, name, link |
catalog | object | The catalog the event is about: id, name, link. Present only on a monitor that watches catalogs |
channel | object | The channel the event is about: id, name, link. Present only on a monitor that watches channels |
trigger | string | What the monitor checks: catalogRun (after each catalog import), channelRun (after each channel run), event (when an event is reported) or schedule (on a schedule) |
run | object or null | The import or channel run the event is about (see below) |
event | object or null | The reported event, on a monitor that reacts to events (see below) |
observed | object | The values the monitor observed (see below). Can be shortened or empty on a very large feed; truncated then says so |
truncated | boolean | true when observed was cut to keep the message under 200 KB: the per-field keys (fillRatePct.<field>, changedCount.<field>, …) are dropped first, then observed is emptied. The counters and every other field are never cut. Present only when true |
A message carries exactly one of catalog and channel; the other is left out. project, run and event are always present and are null when they don't apply, so a receiver can tell "none" from "not in this version". truncated, publishHeld and errorSummary appear only when they have something to say.
Read the message by member name. The order of the members is not part of the contract, and new members can be added to version 1 at any time, so ignore any you don't know. organization.name, project.name, catalog.name and channel.name can be null when Renable couldn't look the name up at the moment the event was recorded; the id next to each is always there.
Every link is an absolute address in the Renable app. monitor.link opens the monitor; catalog.link and channel.link open the catalog or channel; project.link opens the project.
The three events#
type | Sent when | Notes |
|---|
monitor.matched | An import, channel run or reported event where the monitor's condition holds | Every time, whether or not the monitor was already triggered. A monitor with no condition matches every one. Never sent by a monitor on a schedule |
monitor.triggered | The monitor starts to trigger on a catalog or channel | Once per incident. The email, if the monitor sends one, goes out at the same moment |
monitor.cleared | The monitor stops triggering on that catalog or channel | The next monitor.triggered for the same catalog or channel starts a new incident |
A monitor judges each catalog or channel separately, so one monitor can send monitor.triggered for one catalog while another stays quiet. Use monitor.id together with catalog.id or channel.id to match a monitor.cleared to the monitor.triggered it ends.
run#
| Field | Type | What it holds |
|---|
id | string | The run's id |
kind | string | catalog for an import, channel for a channel run |
status | string | How the run ended — a code from the status tables below |
trigger | string or null | What started the run — a code from the trigger table below. Can be null on an older channel run |
startedAt | string or null | ISO 8601 UTC timestamp |
finishedAt | string or null | ISO 8601 UTC timestamp |
publishHeld | boolean | true when a monitor held this channel run's publish. Present only then |
errorSummary | string | Why the run failed, in the words the Activity tab uses. Present only when there is one |
link | string | The catalog's or channel's Activity page, where the run is listed |
counters | object | The run's counts — see the counter tables below |
Which run a message carries depends on what the monitor checks:
trigger | run | event |
|---|
catalogRun | The import that was just checked | null |
channelRun | The channel run that was just checked | null |
event | null | The event that was reported. Can be null on monitor.cleared, when the monitor cleared because none of the events it watches is open any more |
schedule | The latest import or channel run that carried data, or null when there is none | null |
Import statuses (run.kind = catalog)#
| Code | Label in the app | When |
|---|
ok | OK | The import finished cleanly |
issues | Issues | Finished with minor data problems |
warnings | Warnings | Finished with warnings |
error | Errors | The import failed |
not_modified | No changes | The feed hadn't changed since the last import |
retry_scheduled | Retrying | The import failed and will be tried again |
skipped | Skipped | The import didn't run |
interrupted | Interrupted | The import was cut short |
A message sent after an import carries one of the first four. The run of a scheduled check is always ok, issues or warnings. The other codes appear only in a scheduled check's observed.lastRunStatus. A test reports the run a real message would have reported, so it never carries them either.
Channel run statuses (run.kind = channel)#
| Code | Label in the app | When |
|---|
ok | Published | The run finished and published |
warnings | Published with warnings | The run published, with warnings |
blocked | Publish held | A monitor held the publish; the previous feed stays live |
error | Failed | The run failed |
interrupted | Interrupted | The run was cut short |
A message sent after a channel run carries one of the first four. The run of a scheduled check is always ok or warnings. interrupted appears only in a scheduled check's observed.lastRunStatus.
Run triggers (run.trigger)#
| Code | Kind | Label on the Activity tab |
|---|
scheduled | Import or channel run | Scheduled |
manual | Import or channel run | Manual |
publish | Import | Publish |
publish | Channel run | Published |
plan_changed | Import | Plan change |
products_changed | Channel run | Products changed |
template_changed | Channel run | Template changed |
Import counters#
run.counters for an import. All are whole numbers.
| Counter | What it counts |
|---|
rows | Rows read from the feed file, before any were merged or skipped |
products | Distinct products found in the feed |
written | Products created or updated |
created | Products that appeared for the first time |
removed | Products removed because they're no longer in the feed |
excluded | Products dropped by one of the catalog's exclude rules |
collapsed | Products left out because a group rule keeps one product per group |
parseFailed | Rows the import couldn't read at all |
warnings | Distinct kinds of warning the import raised (not a count of products) |
writeFailed | Products that couldn't be saved |
unchanged | Products already up to date — nothing changed since the last import |
After a failed import the counters describe how far it got, so treat them as partial.
Channel run counters#
run.counters for a channel run.
| Counter | What it counts |
|---|
products | Products the run loaded |
entries | Entries the run wrote out |
excluded | Products excluded by the channel's rules |
outOfStock | Entries that are out of stock |
marked | Entries a channel rule marked |
archived | Entries written as archived. null on a run recorded before this was counted |
images | Image renders across the run's entries |
videos | Video renders across the run's entries |
media | An object: total media across the run, reused (already available), new (rendered during this run), pending (still rendering when the run ended) |
After a failed channel run the counters describe how far it got, so treat them as partial.
event#
On a monitor that reacts to events.
| Field | Type | What it holds |
|---|
id | string | The event's id |
severity | string | info, issue, warning or error |
title | string | The event's title, or its message when it has no title |
observed#
The values the monitor observed — the same statistics its condition can compare, keyed by name. A value that couldn't be measured is left out rather than sent as zero: the change figures are missing on a catalog's first import, and a failed import or channel run carries only its failure flag and status. Numbers may carry a decimal point (1200.0), and percentages are plain numbers (-31.2 means −31.2 %). Treat the list below as the common keys, not a closed set.
After a catalog import:
| Key | In the monitor's condition picker |
|---|
importFailed | Import failed (true or false) |
status | Run status — an import status code |
productCount | Product count |
previousProductCount | Previous product count |
productCountDelta | Product count change |
productCountDeltaPct | Product count change % |
createdCount | New products |
removedCount | Removed products |
excludedCount | Excluded products |
collapsedCount | Products grouped into other products |
parseFailedCount | Products that couldn't be read |
warningClassCount | Products with warnings |
fillRatePct.<field> | Fill rate % of a field, one key per field — fillRatePct.price |
fillRatePctDelta.<field> | Fill rate % change of a field since the previous import |
changedCount.<field> | Products whose field changed |
changedPct.<field> | % of products whose field changed |
After a channel run:
| Key | In the monitor's condition picker |
|---|
deployFailed | Publish failed (true or false) |
status | Run status — a channel run status code |
entryCount | Product count |
previousEntryCount | Previous product count |
entryCountDelta | Product count change |
entryCountDeltaPct | Product count change % |
excludedCount | Excluded products |
outOfStockCount | Out-of-stock products |
outOfStockPct | Out-of-stock % |
outOfStockCountDelta | Out-of-stock change |
outOfStockPctDelta | Out-of-stock % change (points) |
archivedCount | Archived products |
archivedPct | Archived % |
archivedCountDelta | Archived change |
archivedPctDelta | Archived % change (points) |
fillRatePct.<field> | Fill rate % of a field |
fillRatePctDelta.<field> | Fill rate % change of a field since the previous run |
On a schedule: the keys of the latest import or channel run that carried data, as above, plus:
| Key | In the monitor's condition picker |
|---|
hoursSinceLastSuccessfulImport | Hours since last successful import |
hoursSinceLastImportAttempt | Hours since last import attempt |
hoursSinceLastDeploy | Hours since last publish |
lastRunStatus | Latest run status — how the most recent import or channel run ended, as a status code |
When an event is reported: the event's own details, which differ from one kind of event to the next. Common keys are eventKind, count, rate, column, target and destination.
Test messages#
A message sent with Send test has "test": true and a fresh id, and is otherwise built the way a real one would be, from real data: the latest import or channel run of the catalog or channel picked in the dialog that a real message would have reported (run is null when there has been none — a test never reports an import with no changes, a skipped or interrupted run, a publish check, or a channel run that only emptied the feed), its newest open event on a monitor that reacts to events, or the values a scheduled check would see right now. Its trigger and the monitor's name are the ones on the page, unpublished changes included. Ignore test messages in production logic, or route them somewhere harmless.
Retries, duplicates and order#
Retries. A delivery is accepted by any 2xx answer. It's tried again, with a growing wait, for about an hour when the address can't be reached, doesn't answer within 10 seconds, or answers 408, 429 or any 5xx. Any other answer fails the delivery at once: other 4xx statuses, every redirect (3xx — redirects are never followed, so enter the final address), and an address that points into a private network. A delivery that fails for good raises an Error alert on the monitor — one per webhook — which clears with the next successful delivery to that webhook, or when the webhook is removed. A delivery that was still waiting when the monitor was paused, or its webhook removed or re-pointed, is not sent at all.
Duplicates. Every retry of one delivery carries the same Idempotency-Key. Very rarely a single attempt can also arrive twice. Store the key and ignore a message whose key you've already handled. When one event goes to several of your webhooks, each delivery has its own key while the body's id stays the same.
Order. Deliveries are sent and retried independently, so they can arrive in any order — a monitor.cleared can overtake the monitor.triggered before it. Order by occurredAt: for monitor.triggered and monitor.cleared it is the moment the monitor triggered or cleared, so it puts an incident's messages in the right order whichever arrives first. Use run.id to recognise messages about the same run.
An example#
A monitor.triggered message after a catalog import, shortened:
{
"type": "monitor.triggered",
"version": 1,
"test": false,
"id": "66f2a1c0e4b0a1b2c3d4e5f6",
"occurredAt": "2026-09-24T12:00:00.000Z",
"monitor": {"id": "66e0f3a9e4b0a1b2c3d4e5f7", "name": "Product count drops sharply", "link": "https://app.renable.com/monitors/66e0f3a9e4b0a1b2c3d4e5f7"},
"organization": {"id": 42, "name": "Nordic Outdoor"},
"project": {"id": "66a1b2c3e4b0a1b2c3d4e5f8", "name": "Nordic Outdoor SE", "link": "https://app.renable.com/projects/66a1b2c3e4b0a1b2c3d4e5f8"},
"catalog": {"id": "153", "name": "Main catalog", "link": "https://app.renable.com/projects/66a1b2c3e4b0a1b2c3d4e5f8/catalogs/153"},
"trigger": "catalogRun",
"run": {
"id": "66f2a1a8e4b0a1b2c3d4e5f9", "kind": "catalog", "status": "ok", "trigger": "scheduled",
"startedAt": "2026-09-24T11:58:12.000Z", "finishedAt": "2026-09-24T11:59:40.000Z",
"link": "https://app.renable.com/projects/66a1b2c3e4b0a1b2c3d4e5f8/catalogs/153",
"counters": {"rows": 8400, "products": 5342, "written": 310, "created": 12, "removed": 2790, "excluded": 40,
"collapsed": 0, "parseFailed": 0, "warnings": 2, "writeFailed": 0, "unchanged": 5032}
},
"event": null,
"observed": {"importFailed": false, "status": "ok", "productCount": 5342.0, "previousProductCount": 8120.0,
"productCountDelta": -2778.0, "productCountDeltaPct": -34.2, "fillRatePct.price": 99.1}
}