Skip to content
Recipes
Skip navigation
Recipes

Standing watches

whisper.watch subscribes to a change in the graph and tells you when it happens — a graph-delta subscription, not a detection.

Published

View as Markdown
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.

actionDoesKeys
CREATE (the default when action is omitted)mints a subscriptionkind, plus the kind's key below
CANCELremoves onesubscription_id
LISTlists the credential's watchesnone

Each kind has a closed key set: the one key named here, and nothing else.

kindWatchesKey
querythe result of a Cypher query you supplyquery
verdictthe verdict on a hosthost
indicatoran indicator identified by hashhash

Creating one

cypher
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

cypher
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.

cypher
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:

actionColumns
CREATEsubscription_id, status, delivery_mode, kind, footprint
LISTsubscription_id, kind, delivery_mode, status, created_epoch
CANCELsubscription_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.