Watches

A watch labels captures as they arrive. You give it a condition and a label, and every request that matches is tagged at the moment it is stored rather than sorted out afterwards. On a busy endpoint that is the difference between a readable list and a wall.

What a watch does

A watch belongs to one endpoint and has a name, a condition written in JSONata, and an optional label. When a capture lands on that endpoint, after signature validation and the plan checks have passed, every enabled watch is evaluated against it. Each match bumps the watch’s counter and records the time. The first matching watch that carries a label, in the order the watches were registered, stamps its label on the capture. The label is then part of the record: it shows on the capture row, the capture list filters on it, and the digest groups by it.

That is the whole effect. A watch sends nothing, forwards nothing and changes nothing about the payload, so it is always safe to add to a live endpoint. Evaluation is bounded to a quarter of a second per watch and can never fail a capture; a watch that errors records the error and the capture is stored anyway.

Setting one up

In the dashboard, open the endpoint, choose the Watches tab and press Add watch. Three fields: a name, the condition, and the label to stamp. From an agent, the register_watch tool takes the same three. There is no CLI command for it.

A worked example for a Stripe endpoint: name it "Failed charges", set the condition to

$body.type = "charge.failed"

and the label to "charge failed". From then on every failed charge arrives already labelled, and the digest tells you how many there were this hour without opening one.

What you can match on

The condition is a JSONata expression over three variables. $body is the parsed JSON body (or the raw text when the body is not JSON). $headers is the request headers with their original casing, each value an array. $query is the query string as a flat object.

$body.type = "charge.failed"                  # an event type
$headers."Stripe-Signature"[0] != null        # a header is present (note the casing and the [0])
$query.env = "production"                     # a query-string value
$body.data.object.amount > 100000             # a number in the payload

Two things the condition cannot see. Secrets: a condition that mentions $secrets is refused when you save it, because watches run on every incoming request before anyone has signed in. And other captures: a watch sees one request at a time.

One header is special. $headers.fp_signer is set by the server to the seat or contributor that signed the request, or null when it was unsigned, and a sender cannot forge it. It is how a room endpoint can label posts by who made them.

Three things worth stating outright

  • Labels apply at capture time, not retroactively. A watch created today does not label what arrived yesterday, and there is no apply-to-existing action. You can still label an old capture by hand from its row.
  • Pausing keeps the slot; only deleting frees it. A disabled watch keeps its counters and its place in the plan limit. Deleting a watch is a dashboard action, deliberately not available to tokens or agents, and it removes the counters for good while leaving every label it already stamped in place.
  • A broken condition is visible, not silent. If a condition throws at evaluation time the watch records the last error and keeps running; it is not switched off for you. The error shows in the dashboard and in list_watches, and clears the next time the condition matches or the condition is edited.

Editing, pausing and deleting

Rename a watch or change its condition or label from its row in the dashboard; counters survive an edit, and renaming does not use a new slot. The toggle on the row pauses and resumes it, and the set_watch_enabled tool does the same from an agent. The trash icon deletes it after a confirmation.

Before you claim a session

An anonymous session can register up to three watches. They are checked for validity when you save them but they sleep: nothing is evaluated until the session is claimed. On claim they move to your real endpoint, enabled oldest first up to your plan’s limit, with any overflow left disabled rather than dropped.

Limits

Watches are metered per endpoint, from three on the free plan, and the limit counts paused watches too. Registering one past the limit is refused with a message naming the count and pointing at the dashboard. The limit for every plan is on the plans and limits page; it is the one limit that stays bounded on every plan, because every watch is evaluated on every capture that arrives.

Where this fits

Watches label what arrives. Transformations reshape it, and auto-forward delivers it. The three are separate on purpose: labelling changes nothing about the payload, so a watch is always safe to add to a live endpoint.