Monitor Webhook Payload Reference

The exact message a monitor's webhook sends — the request and its headers, every field of the JSON body, the run status and trigger codes, the counters per import and channel run, and how retries, duplicates and ordering work.

Updated September 24, 2026

This is the reference for the system that receives a monitor's webhook — a CRM, a ticketing tool, an automation platform, or your own code. How to add a webhook to a monitor is in Send monitor events to a webhook.

Every message is version 1 of a fixed format. New fields may be added to it without a version change, so ignore fields you don't know. A change that would break an existing receiver comes with a new version number.

The request#

Each event is sent to each webhook that subscribes to it as one HTTPS request.

PartValue
MethodPOST to the webhook's address, exactly as entered, query string included
Content-Typeapplication/json; charset=utf-8
User-AgentRenable-Monitors/1
Idempotency-KeyThe delivery's id. The same on every retry of one delivery, different for each webhook the event goes to
X-Renable-DeliveryThe delivery's id, the same value as Idempotency-Key
X-Renable-EventThe event type: monitor.matched, monitor.triggered or monitor.cleared
BodyThe 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#

FieldTypeWhat it holds
typestringThe event: monitor.matched, monitor.triggered or monitor.cleared (see below)
versionnumber1
testbooleantrue for a message sent with Send test, false otherwise
idstringThe event's id. Every webhook the event goes to receives the same id; a test carries a fresh one
occurredAtstringWhen the event happened, as an ISO 8601 UTC timestamp (2026-09-24T12:00:00.000Z)
monitorobjectThe monitor: id, name, link
organizationobjectYour organization: id (a number), name
projectobject or nullThe project the catalog or channel belongs to: id, name, link
catalogobjectThe catalog the event is about: id, name, link. Present only on a monitor that watches catalogs
channelobjectThe channel the event is about: id, name, link. Present only on a monitor that watches channels
triggerstringWhat the monitor checks: catalogRun (after each catalog import), channelRun (after each channel run), event (when an event is reported) or schedule (on a schedule)
runobject or nullThe import or channel run the event is about (see below)
eventobject or nullThe reported event, on a monitor that reacts to events (see below)
observedobjectThe values the monitor observed (see below). Can be shortened or empty on a very large feed; truncated then says so
truncatedbooleantrue 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#

typeSent whenNotes
monitor.matchedAn import, channel run or reported event where the monitor's condition holdsEvery 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.triggeredThe monitor starts to trigger on a catalog or channelOnce per incident. The email, if the monitor sends one, goes out at the same moment
monitor.clearedThe monitor stops triggering on that catalog or channelThe 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#

FieldTypeWhat it holds
idstringThe run's id
kindstringcatalog for an import, channel for a channel run
statusstringHow the run ended — a code from the status tables below
triggerstring or nullWhat started the run — a code from the trigger table below. Can be null on an older channel run
startedAtstring or nullISO 8601 UTC timestamp
finishedAtstring or nullISO 8601 UTC timestamp
publishHeldbooleantrue when a monitor held this channel run's publish. Present only then
errorSummarystringWhy the run failed, in the words the Activity tab uses. Present only when there is one
linkstringThe catalog's or channel's Activity page, where the run is listed
countersobjectThe run's counts — see the counter tables below

Which run a message carries depends on what the monitor checks:

triggerrunevent
catalogRunThe import that was just checkednull
channelRunThe channel run that was just checkednull
eventnullThe 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
scheduleThe latest import or channel run that carried data, or null when there is nonenull

Import statuses (run.kind = catalog)#

CodeLabel in the appWhen
okOKThe import finished cleanly
issuesIssuesFinished with minor data problems
warningsWarningsFinished with warnings
errorErrorsThe import failed
not_modifiedNo changesThe feed hadn't changed since the last import
retry_scheduledRetryingThe import failed and will be tried again
skippedSkippedThe import didn't run
interruptedInterruptedThe 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)#

CodeLabel in the appWhen
okPublishedThe run finished and published
warningsPublished with warningsThe run published, with warnings
blockedPublish heldA monitor held the publish; the previous feed stays live
errorFailedThe run failed
interruptedInterruptedThe 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)#

CodeKindLabel on the Activity tab
scheduledImport or channel runScheduled
manualImport or channel runManual
publishImportPublish
publishChannel runPublished
plan_changedImportPlan change
products_changedChannel runProducts changed
template_changedChannel runTemplate changed

Import counters#

run.counters for an import. All are whole numbers.

CounterWhat it counts
rowsRows read from the feed file, before any were merged or skipped
productsDistinct products found in the feed
writtenProducts created or updated
createdProducts that appeared for the first time
removedProducts removed because they're no longer in the feed
excludedProducts dropped by one of the catalog's exclude rules
collapsedProducts left out because a group rule keeps one product per group
parseFailedRows the import couldn't read at all
warningsDistinct kinds of warning the import raised (not a count of products)
writeFailedProducts that couldn't be saved
unchangedProducts 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.

CounterWhat it counts
productsProducts the run loaded
entriesEntries the run wrote out
excludedProducts excluded by the channel's rules
outOfStockEntries that are out of stock
markedEntries a channel rule marked
archivedEntries written as archived. null on a run recorded before this was counted
imagesImage renders across the run's entries
videosVideo renders across the run's entries
mediaAn 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.

FieldTypeWhat it holds
idstringThe event's id
severitystringinfo, issue, warning or error
titlestringThe 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:

KeyIn the monitor's condition picker
importFailedImport failed (true or false)
statusRun status — an import status code
productCountProduct count
previousProductCountPrevious product count
productCountDeltaProduct count change
productCountDeltaPctProduct count change %
createdCountNew products
removedCountRemoved products
excludedCountExcluded products
collapsedCountProducts grouped into other products
parseFailedCountProducts that couldn't be read
warningClassCountProducts 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:

KeyIn the monitor's condition picker
deployFailedPublish failed (true or false)
statusRun status — a channel run status code
entryCountProduct count
previousEntryCountPrevious product count
entryCountDeltaProduct count change
entryCountDeltaPctProduct count change %
excludedCountExcluded products
outOfStockCountOut-of-stock products
outOfStockPctOut-of-stock %
outOfStockCountDeltaOut-of-stock change
outOfStockPctDeltaOut-of-stock % change (points)
archivedCountArchived products
archivedPctArchived %
archivedCountDeltaArchived change
archivedPctDeltaArchived % 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:

KeyIn the monitor's condition picker
hoursSinceLastSuccessfulImportHours since last successful import
hoursSinceLastImportAttemptHours since last import attempt
hoursSinceLastDeployHours since last publish
lastRunStatusLatest 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}
}