Standing watches
whisper.watch subscribes to a change in the graph and tells you when it happens — a graph-delta subscription, not a detection.
On this page (8)
Standing watches Documentation
A watch is a subscription to a change in the graph. You describe something you want to know about, and Whisper tells you when the graph's answer to it changes.
What a watch is not
Warning: In this product, an alert is a watch notification, not a detection. A watch observes changes in the graph: a new listing, a new resolution, a changed origin. It does not observe events on your network, because Whisper has no sensor on your network and no schema for host telemetry. Nothing about a watch fires on something a machine of yours did.
If you want detections on your own events, that is your SIEM's job, and the integrations are how a Whisper verdict reaches it.
The contract
whisper.watch takes exactly one map argument. The map carries an action, and for a create, a kind plus the one key that kind requires.
action | Does | Keys |
|---|---|---|
CREATE (the default when action is omitted) | mints a subscription | kind, plus the kind's key below |
CANCEL | removes one | subscription_id |
LIST | lists the credential's watches | none |
Each kind has a closed key set: the one key named here, and nothing else.
kind | Watches | Key |
|---|---|---|
query | the result of a Cypher query you supply | query |
verdict | the verdict on a host | host |
indicator | an indicator identified by hash | hash |
Creating one
CALL whisper.watch({action: "CREATE", kind: "verdict", host: "example.com"})
YIELD subscription_id, status, delivery_mode, kind, footprint
RETURN subscription_id, status, delivery_mode, kind
The inner query of a kind: query watch is planned but not executed at creation time. Creating a watch is a pure read-and-enqueue, which is why it composes inside UNWIND ... CALL without running your query once per row.
Over HTTP, send an Idempotency-Key header with a create. A retried request that carries the same key does not mint a second subscription; without it, a retry after a dropped response leaves you with two watches on the same thing.
Listing and cancelling
CALL whisper.watch({action: "LIST"})
YIELD subscription_id, kind, delivery_mode, status, created_epoch
RETURN subscription_id, kind, delivery_mode, status, created_epoch
This one ships static rather than runnable, and the reason is the answer: for a credential holding no watches it returns zero rows, and a Run button that comes back empty teaches a reader the product is empty. An empty result here means this credential holds no watches, and that is not an error, but a button cannot say so.
CALL whisper.watch({action: "CANCEL", subscription_id: "<the id from LIST>"})
YIELD subscription_id, status, detail
RETURN subscription_id, status, detail
kind is required on a create and forbidden on a cancel or a list. Both discriminators are closed sets and both fail closed: a rejected call mints nothing, so a typo leaves no half-created subscription behind.
The columns depend on the action
This is the one thing about whisper.watch that surprises people, so it is stated as a table:
action | Columns |
|---|---|
CREATE | subscription_id, status, delivery_mode, kind, footprint |
LIST | subscription_id, kind, delivery_mode, status, created_epoch |
CANCEL | subscription_id, status, detail |
Name the columns you want in YIELD. A bare CALL whisper.watch(...) returns whichever set the action produced, and a client that assumes one shape breaks on another.
Delivery
delivery_mode on the create and list rows names the channel a watch delivers on. Read it back rather than assuming one: the notification arrives on the channel the row names, and nothing about it requires an inbound endpoint of yours to be exposed.
What a watch cannot see
- Anything not in the graph. A watch on a host tells you when Whisper's view of it changes, not when the host changes.
- The moment of change. A watch fires when the graph's answer changes, which is when ingestion observed it, not when it happened in the world.
- Your own events. Restated because it is the mistake the word "alert" invites.
Related
- Exporting at volume — the other bulk primitive
- Agents & MCP — the agent surface a verdict change reaches
- Integrations — getting a verdict into your SIEM