← All guides
Alerting

How Diff Predicates Decide What Alerts You

A change in a field and an alert are two different things. The predicate is the rule in between - here is every type, a worked example for each, and the gate that quietly blocks the ones people expect to fire.

Verid Guides·7 min read

Why a changed page doesn't always mean an alert

Every monitor runs the same two steps on every check: extract the fields, then compare them against the previous run. What happens after that is a separate decision, made by the monitor's diff predicate. The comparison tells Verid what changed. The predicate decides whether that change is worth telling anyone about.

This is the difference between "the page changed" and "the price dropped by more than 5%." A monitor with any_field_changes fires on any difference at all. A monitor with field_decreases_by_percent on the price field stays quiet through ten runs of unrelated text changes and only fires the moment the number actually drops. Same page, same comparison, two completely different alert behaviours, chosen entirely by which predicate is attached.

Every run records the comparison it made against the previous run - which fields changed, and their before/after values - the same shape a webhook delivery carries in its diff block:

{
  "fields_changed": ["price"],
  "before": { "price": "24.99" },
  "after": { "price": "19.99" }
}

Two things about this shape matter for everything below. First, fields_changed only lists fields whose value actually differs from the prior run - unchanged fields aren't in it. Second, before and after only ever carry the fields that changed, not the full extracted snapshot. An unchanged field is simply absent from both, not present with an equal value. That second point explains a failure mode later in this guide.

If the predicate matches, a delivery goes out to every notification endpoint on the monitor. If it doesn't, the run is still recorded in full - extracted values, the comparison, everything - but nothing is sent.

The nine predicate types

Fields are referenced by the name you gave them in your extraction config.

any_field_changes

{ "type": "any_field_changes" }

Fires whenever at least one field differs from the previous run. The simplest predicate, and the right one for "tell me the instant anything on this page moves." Pairs naturally with the full page hash extraction method.

field_changes

{ "type": "field_changes", "field": "tag_name" }

Fires only when the named field changed. Every other field can move freely without triggering anything. This is the one to reach for when you're tracking one specific value - a release tag, a status string - and don't care what else on the page shifts around it.

field_increases_by_percent

{ "type": "field_increases_by_percent", "field": "price", "threshold": 5 }

Fires when the field changed and the percent increase from the previous value to the new one is at least threshold. threshold is a plain number, not a fraction - 5 means 5%, not 0.05. The percent move is measured against the size of the previous value, so a previous value of exactly 0 never satisfies a percent predicate - there's no percentage increase from zero that means anything, so it's treated as never firing rather than as an error.

field_increases_by_absolute

{ "type": "field_increases_by_absolute", "field": "url_count", "threshold": 1 }

Same gate, but compares the raw increase against threshold with no percentage math. threshold: 1 gives you "one or more new entries," which is the common shape for sitemap URL counts, comment counts, or subscriber counts.

field_decreases_by_percent

{ "type": "field_decreases_by_percent", "field": "price", "threshold": 10 }

Mirror image of the percent-increase case: fires on a drop of at least threshold percent from the previous value. Same zero-baseline rule applies. This is the standard price-drop predicate.

field_decreases_by_absolute

{ "type": "field_decreases_by_absolute", "field": "in_stock", "threshold": 5 }

Fires when the value dropped by at least threshold in raw terms. Useful for inventory or follower-count drops where you care about the amount lost, not a percentage of a number that might itself be small.

field_matches_regex

{ "type": "field_matches_regex", "field": "status", "pattern": "^(error|failed|degraded)" }

Fires when the field's new value matches the regular expression in pattern. If the field isn't present in the new value, or pattern itself isn't a valid regular expression, the predicate simply doesn't fire rather than erroring the run.

field_equals

{ "type": "field_equals", "field": "availability", "value": "In Stock" }

Fires when the field's new value exactly equals the literal value. The comparison is type-strict: a JSON string "true" never equals the boolean true, no matter how you read the field. Comparing whole objects (which mostly comes up with JSONPath or AI extraction fields) works, but only if the object's fields are in the same order as value - it isn't a deep, order-independent comparison.

composite (AND / OR)

{
  "type": "composite",
  "operator": "AND",
  "conditions": [
    { "type": "field_changes", "field": "tag_name" },
    { "type": "field_equals", "field": "prerelease", "value": false }
  ]
}

Combines any number of the other eight predicates - including other composite predicates, nested as deep as you need - under AND (every condition must be true) or OR (any one is enough). The common patterns are "field changed AND another field equals a specific value" (skip prereleases, skip out-of-stock states) and "dropped by X% OR went out of stock" to catch two different signals with one monitor instead of two.

The gate that catches everyone

Every one of the eight non-composite predicates only fires for a field that actually changed on that run. For field_changes and both directions of _by_percent / _by_absolute, that's an explicit, first check - if the field isn't among the ones that changed, the predicate stops there without even looking at the value. field_matches_regex and field_equals reach the same result a different way: since the "new value" they read is only ever populated for fields that changed, an unchanged field looks the same as a missing one, and both predicates decline to fire on a missing field.

Either way, the practical rule is the same: a value being correct is not enough - it also has to have changed on this specific run. This is the single most common reason a correctly-configured predicate "never fires." The threshold, the regex, the equality check - all of it is fine, but the field held the same value as last time, so the predicate never got as far as evaluating its own condition.

The first run is not silent

It's worth being direct about this because it contradicts a common assumption: the first run of a new monitor can alert. A brand-new monitor has nothing to compare against, so Verid treats every extracted field as "new" on that first check - every field counts as changed, exactly as if it had just flipped from one value to another.

In practice this means an any_field_changes predicate, or a field_changes predicate on a field that extracts successfully, fires and delivers on the very first check, before there's any real history to compare against. If you don't want a delivery the moment you save a new monitor, use a percent- or absolute-threshold predicate instead - those can't fire on a first run, because there's no previous numeric value to measure the move from.

Reading a run against its predicate

Every run stores its comparison and its delivery outcome together, so you don't have to reconstruct anything by hand to see why a run did or didn't alert. If a run's diff shows the field you expected in fields_changed, with the values you expected, and no delivery went out, the predicate's own condition - a threshold, a regex, an exact-match value - is the next thing to check, not the extraction.

What to read next

Ready to try it? Point Verid at your URL and get a signed alert on every change. 200 free runs a month, no credit card.

Try Verid for free

200 free runs a month, no credit card required.