Changelog

What's new in Verid

Every major change, newest first - new capability, capabilities that materially changed, and defects that were producing wrong output. Each entry says what shipped, how it works, and where its limits are, dated to the day it went out.

Newcapability that did not exist before
Improvedan existing capability materially changed
Fixeda defect that was producing wrong output
August 2026
  1. ImprovedAugust 15, 2026

    Comparing two URLs in the playground now compares their content, not their markup

    The playground’s Compare two URLs mode used to run a line diff of both pages’ HTML, which on two different sites reports almost every line as changed. It now reads both pages into a content profile — title, meta description, h1, every h2 and h3, prices, call-to-action labels, length — compares those field by field, and shows the readable content of both pages side by side underneath.

    Diffing a page against its own past works because the markup barely moves: the same template renders the same tree, and the handful of lines that changed are the change. Diffing two different sites is a different problem wearing the same clothes. One page is a Next.js tree of hashed class names, the other is an Elementor tree of `elementor-element-cd45e99` wrappers, and nothing lines up. Run the old compare over three real competitor pricing pages and it reports between 97.5% and 98.3% of lines as changed. The rendered diff is capped at 200 lines, so what a visitor actually saw was two hundred rows of red navigation markup, and the prices they came to compare were thousands of lines further down.

    The compare mode no longer diffs markup at all. Both pages are read into a small profile and the profiles are compared field by field: page title, meta description, the `h1`, every `h2` and `h3` in page order, the prices on the page, the call-to-action labels, and the visible word count. Each row says whether the two sides are the same, differ, or exist on only one of them, and within the list rows every item the other page does not have is outlined — so on two pricing pages, the plan names and the amounts that only one side offers are the part that stands out.

    Prices are currency-anchored on purpose. A bare number on a pricing page is a seat count, a retention window or a year far more often than it is a price, so an amount is only collected when it carries a symbol (`$`, `€`, `£`, `¥`, `₹`) or a currency code (USD, EUR, GBP, JPY, INR, PKR, CAD, AUD), or when it is declared in `[itemprop="price"]` markup or a JSON-LD `price`, `lowPrice` or `highPrice` field, in which case the accompanying `priceCurrency` is used to format it. Calls to action come from real buttons and button-styled links, plus any link whose text reads as an action, which is how a primary button styled with a class name no pattern could guess still gets found.

    Before either page is read, site chrome is removed: `nav`, `footer`, `header`, `aside`, the matching ARIA landmark roles, dialogs and modals, anything `hidden` or `aria-hidden`, and any element whose class or id mentions cookies or consent. This is not tidying. Without it, a real comparison of two monitoring vendors reported "Accept all" and "Accept required" as one vendor’s calls to action and the heading of a hidden quote form as its `h1`. Removal is skipped when it would leave the page with under 200 characters of text, because some sites do put the whole body inside a `<header>`. What is left is then narrowed to the first of `main`, `[role="main"]`, `article`, `#content` or `#main` that holds real content.

    Underneath the table, both pages are rendered as a readable outline and placed side by side: headings at their level, list items as bullets, quotes, and button labels, up to 140 lines per page. It is not an aligned diff and does not try to be — two marketing pages have no common structure to align against — it is there so that once the table has told you which fields differ, you can read what the other page actually says. Word count is reported in the table but deliberately not counted as a difference, since two pages of different length is the normal case rather than a finding.

    Nothing else changed. Comparing two URLs still costs two of the five hourly checks because it is still two fetches, both bodies still land in the page cache so either side can be opened in the Extract fields tab for free, and the single-URL check and its Internet Archive diff still use the markup line diff, which is the right tool for the same page over time. When two URLs normalize to exactly the same text the comparison says so directly, which is the usual tell for a mirror or a redirect target.

    What it compares
    Page title, meta description, `h1`, every `h2` and `h3` (up to 24), prices (up to 10), call-to-action labels (up to 8), and visible word count.
    Why the old diff had to go
    Measured 97.5%–98.3% of lines changed across three real competitor pricing-page pairs, against a 200-line render cap.
    Price detection
    Amounts carrying `$ € £ ¥ ₹` or USD/EUR/GBP/JPY/INR/PKR/CAD/AUD, plus `[itemprop="price"]` and JSON-LD `price`, `lowPrice` and `highPrice` formatted by their `priceCurrency`.
    Stripped before reading
    `nav`, `footer`, `header`, `aside`, `[role="navigation"]`, `[role="banner"]`, `[role="contentinfo"]`, `dialog`, `[role="dialog"]`, `[aria-modal="true"]`, `[hidden]`, `[aria-hidden="true"]`, and any cookie or consent class or id — skipped if it would leave under 200 characters of text.
    Side-by-side content
    Both pages as a readable outline, up to 140 lines each, collapsed behind "Read both pages side by side".
    Cost
    Unchanged: 2 of the 5 hourly checks, and both fetched bodies stay cached for the Extract fields tab.
  2. NewAugust 14, 2026

    The playground now gives you a diff, your extracted fields, and the alert on the first check

    A first check of a URL used to return a fingerprint and an instruction to come back in six hours. It now diffs the live page against the Internet Archive’s most recent old capture, and adds tabs for testing selectors, previewing the webhook, Slack and email a monitor would send, testing the alert rule, copying the API call, and capturing a live screenshot.

    The playground at /playground has always run the real detection engine — the same static fetch, the same HTML normalization, the same `full_page` extraction a saved monitor runs. What it could not do was answer the question people arrive with. Detecting a change needs two versions of a page, and a visitor pasting a URL nobody has checked before has one. So the honest answer was "baseline captured, check again within six hours", which is a fingerprint, a byte count and homework. Almost nobody came back.

    The first check now falls back to the Internet Archive. Verid queries the public CDX index for that URL, picks the most recent capture that is at least seven days old (collapsing consecutive captures with an identical content digest, so the candidates are real revisions rather than forty copies of the same page), fetches the archived HTML through the `id_` modifier that returns original bytes with no replay toolbar, runs it through the same line renderer as the live page, and diffs the two. The result is a genuine added/removed line list on the very first submission, labelled with the capture date and linked back to the Wayback replay. Where the archive holds nothing, the old baseline message is shown as before, and the lookup is capped at five seconds for the index and eight for the capture so a slow archive never delays or fails the check itself. Lookups, including "no capture exists", are cached for 24 hours per URL.

    Below the verdict, the result is now a workspace rather than a single panel. **Extract fields** runs CSS selectors, XPath, regex and JSONPath against the page — reusing the HTML already fetched by the check, so iterating on a selector costs none of your five hourly checks and re-scrapes nobody’s site. It opens pre-filled: Verid tries a list of candidate expressions against your page (schema.org price and availability markup, Open Graph product price, prices inside embedded JSON, `h1`, `title`, `time[datetime]`, semver strings) and offers only the ones that actually returned a value, with the value they found. When an archived copy is available the same expressions run against it too, so a field shows its real before and after — 49.00 struck through, 59.00 beside it.

    **Alert preview** shows what a delivery would contain: the webhook body built by the same `buildDeliveryPayload()` the worker uses, plus Slack and email renderings of the same diff. The `Verid-Signature` header is shown in its true `t={timestamp},v1={hex}` format and labelled as an example — an anonymous visitor has no monitor and therefore no signing secret, and a plausible-looking fake hex string would be worse than none. Alongside it, the rule tester runs the real `shouldDeliver()` predicate evaluator against your extracted values, so you can set "price drops by at least 5%" and see whether it would have fired on the change in front of you. **API** renders the `POST /v1/monitors` call that recreates the whole session in cURL, the Node SDK, Python, or raw JSON, generated by the same payload builder the dashboard uses.

    Two more ways in. A **Compare two URLs** mode diffs one page against another instead of against its own past — your pricing page against a competitor’s — which needs no history at all and so always produces something on the first try; it costs two of the five hourly checks because it is two fetches. And the **Screenshot** tab renders your page in a real headless browser through the existing preview worker and returns the full-page image, in exchange for an email address. That one is gated because it is the only part of the playground that occupies a browser and writes to object storage; the address is used for one message containing the image, no account is created, and it is capped at two per day per visitor.

    Results are now shareable. Every check writes to a cache key derived from the URL, and /playground/r/{key} renders that stored result — the diff, the tabs, all of it — for 24 hours, so a check can be sent to a colleague who lands on a working demo instead of a screenshot. Those pages are `noindex, follow`: useful to a recipient, not something that belongs in a search index.

    First-check diff
    Live page vs the newest archive.org capture at least 7 days old, ignoring anything over 3 years. 5s budget for the CDX index, 8s for the capture, cached 24h per URL.
    Selector testing
    CSS, XPath, regex and JSONPath, up to 6 fields at a time, run against the cached page body — 40 attempts per hour per visitor, and none of them consume one of the 5 hourly checks.
    Suggested expressions
    Every candidate is executed against your page and shown only if it returned a value: `[itemprop="price"]`, `.price`, `product:price:amount`, `"price":` inside embedded JSON, `[itemprop="availability"]`, in-stock wording, `h1`, `<title>`, `og:title`, `<time datetime>`, and semver.
    Alert preview
    Real `buildDeliveryPayload()` output with `id`, `version`, `monitor_id`, `run_id`, `fired_at`, `diff.fields_changed`, `diff.before`, `diff.after`, and `monitor`. Signature shown as `Verid-Signature: t=…,v1=…`, labelled an example.
    Rule tester
    Runs `shouldDeliver()` — the predicate evaluator monitors use — over any_field_changes, field_changes, and the percent/absolute increase and decrease predicates.
    Compare two URLs
    Two fetches, so it uses 2 of the 5 hourly checks.
    Live screenshot
    Full-page headless browser render, 2 per day per visitor and 3 per day per email. One email, no account, no marketing sequence.
    Shared results
    /playground/r/{key}, cached 24 hours, `noindex, follow`.
  3. NewAugust 11, 2026

    You can switch the residential proxy fallback off for your account

    Settings now carries a "Residential proxy fallback" switch. It controls whether a blocked check is retried through the residential proxy network or simply fails, and it is on by default for plans that include proxy bandwidth.

    Verid fetches a page in up to three layers. A plain HTTP request comes first, a stealth browser second, and if both come back blocked, a request through a residential proxy network third. The third layer is what keeps monitors alive on sites that refuse datacenter traffic outright, and until now it was applied automatically whenever your plan included proxy bandwidth, with no way to decline it.

    It is now a switch, at Settings → Scraping → Residential proxy fallback, and it defaults to on. Turn it off and the fetcher stops after the browser layer: a check that would have been retried through the proxy instead fails with a clear reason on the run, and no proxy bandwidth is spent. Nothing else about the check changes — the first two layers, the extraction, the diff and the delivery all behave exactly as before. The setting is per account rather than per monitor, because the proxy is a fallback the fetcher reaches for on its own rather than something you configure on a particular URL.

    Two reasons to turn it off. Proxy requests are metered against your plan's monthly bandwidth and are noticeably slower than a direct fetch, so if you would rather a stubborn site simply fail than quietly consume that budget, the switch is how you say so. And a proxied request leaves from a residential IP in another country, which some sites answer with regional pricing, a different currency, or a consent interstitial — a monitor that started reporting changes it should not have been seeing may be reading a page fetched from somewhere else entirely.

    On Free and Lite the switch is present but locked off, because those plans ship no proxy bandwidth to spend. The API refuses to enable it on those tiers for the same reason, so the stored value can never claim a capability the fetcher would ignore. Upgrading to a plan that includes proxy bytes turns it on without any further action.

    The failure message on a blocked run now distinguishes the two cases. If your plan has no proxy budget it says so and points at the plans; if the proxy is available but switched off it says that instead and points at Settings. Previously both produced the same "upgrade required" line, which sent paying customers to the pricing page over a setting they controlled.

    Where
    Settings → Scraping → "Residential proxy fallback".
    Default
    On, for every plan that includes proxy bandwidth. Existing monitors are unaffected.
    API
    `GET /v1/settings` returns `{ proxy_enabled, proxy_available, tier }`; `PATCH /v1/settings` with `{"proxy_enabled": false}` switches it off.
    Locked on
    Free and Lite — both ship 0 MB of proxy bandwidth, so the switch reads off and cannot be enabled.
    Monthly proxy bandwidth
    Starter 50 MB, Pro 500 MB, Scale 5 GB. Free and Lite none.
    Applies to previews too
    The selector test in the monitor form reaches the same fetch layers a saved monitor would, so a URL cannot pass its preview and then fail on its first real check.
  4. FixedAugust 9, 2026

    Your webhook signing secret is now readable, so signatures can actually be verified

    Every webhook delivery has always carried an HMAC-SHA256 `Verid-Signature` header, but the key needed to check it was never exposed anywhere. `GET /v1/monitors/{id}/signing-secret` returns it, and the monitor page shows it under Config.

    Verid signs every webhook delivery, the signature format is documented, and the docs carry verification snippets in five languages. All of those snippets take the signing secret as an argument — and there was no way to get it. The secret is generated per monitor the first time you attach a webhook delivery, encrypted with the platform key, and stored as `webhook_signing_secret_enc`. That encrypted value was what monitor responses returned, which is unusable by definition, and the plaintext appeared in no API response, no dashboard screen, and no part of the documentation. The verification step every integration guide insists on could not be completed by anyone.

    There is now a dedicated endpoint for it. `GET /v1/monitors/{id}/signing-secret` returns `{"secret": "…"}` for a monitor you own, or `{"secret": null}` when the monitor has no webhook delivery and therefore nothing signed to verify. The monitor detail page shows the same value behind a "Reveal secret" control in its Config tab, next to a copy button, for monitors that deliver webhooks. Read it once, put it in your own secret manager, and pass it to the verification function as the docs describe.

    The encrypted value stopped being returned at the same time. `GET /v1/monitors` and `GET /v1/monitors/{id}` no longer include `webhook_signing_secret_enc` in their payloads — it was of no use to a caller, and dropping it keeps the ciphertext out of every response body, log line, and client-side cache that a monitor read touches. The secret is now sent only when it is explicitly requested.

    Nothing changes about how deliveries are signed: same `t={timestamp},v1={signature}` header, same HMAC-SHA256 over `{timestamp}.{raw_body}`, same secret as before. Existing monitors keep the secret they already had, so integrations that somehow already had the value keep working unchanged.

    Endpoint
    `GET /v1/monitors/{id}/signing-secret` → `{"secret": string | null}`.
    Dashboard
    Monitor → Config → Webhook verification → "Reveal secret". Shown only for monitors with a webhook delivery.
    Removed from payloads
    `webhook_signing_secret_enc` no longer appears in monitor list, detail, create, update, pause, resume, or from-template responses.
    Signature unchanged
    `Verid-Signature: t={timestamp},v1={signature}`, HMAC-SHA256 of `{timestamp}.{raw_body}`, 300-second tolerance recommended.
  5. FixedAugust 9, 2026

    CSS extraction ignores inline scripts, and malformed API requests return 400 instead of 500

    A CSS selector matching a container no longer pulls inline JSON-LD and CSS source into the extracted value, the sitemap template returns a real date instead of a match count, and API requests with an invalid or missing JSON body now fail as client errors rather than server errors.

    CSS extraction read an element with the DOM's notion of text content, which includes the source of any `<script>` and `<style>` inside it. A selector as ordinary as `main` — the default in the generic CSS selector template — returned a page's inline JSON-LD block as its "content". That is both useless as a value and actively harmful as a change signal: structured data and inline styles shift on every deploy, so the monitor reported content changes for pages whose visible copy had not moved. Script, style, noscript, and template contents are now stripped before the text is taken, and only from descendants of the matched element — a selector aimed directly at a `<script>` tag, which is how you would extract a JSON-LD blob deliberately, still returns its contents.

    The sitemap template had a related problem in its second field. Regex extraction returns a captured value when the pattern has a capture group, and a count of matches when it does not. The template's `last_modified_latest` pattern had no capture group, so the field could only ever return a number — the same count as `url_count` — under a name promising a timestamp. The pattern now captures, and the field is named `first_lastmod` because a regex returns the first match in document order and sitemaps are not required to be sorted. `url_count` and the template's change condition are unchanged, and monitors already created from this template keep their own copy of the old configuration until you edit them.

    Separately, several API endpoints reported the caller's mistake as our failure. Parsing the request body threw on absent or malformed JSON, and the uncaught error became `500 INTERNAL_ERROR` — which tells an integrator the service is broken and, on our side, files a client mistake as a server incident. Bodies are now parsed through one helper that raises a proper validation error, so `POST /v1/monitors`, `POST /v1/keys`, `POST /v1/keys/{id}/rotate`, `POST /v1/monitors/{id}` and the selector-test and visual-suggest endpoints return `422 VALIDATION_ERROR` for a missing body and `400 INVALID_JSON` for a body that is not valid JSON.

    One consequence is worth planning for: an existing monitor whose CSS field was picking up script or style source will extract a different value on its next check than it stored on its last one, and that counts as a change. Expect a single one-off notification on those monitors, after which the field settles on the visible text and stops moving with every unrelated deploy.

    Key rotation picked up a usable default in the process. `grace_period_hours` was required, so a bare `POST /v1/keys/{id}/rotate` with no body failed outright; it now defaults to 24 hours of overlap, letting the old key keep working while you roll the new one out. Pass `0` to revoke the old key the moment the new one is issued, which is what the dashboard does.

    CSS extraction
    `<script>`, `<style>`, `<noscript>` and `<template>` contents are excluded from a matched element's text; selecting one of those tags directly still returns its contents.
    Sitemap template
    `last_modified_latest` → `first_lastmod`, pattern `<lastmod>([^<]+)</lastmod>`. `url_count` and the `field_changes` condition on it are unchanged.
    Error codes
    Missing body → `422 VALIDATION_ERROR`; unparseable body → `400 INVALID_JSON`. Previously `500 INTERNAL_ERROR` for both.
    Key rotation
    `grace_period_hours` now defaults to 24 (range 0–168). `0` revokes the previous key immediately.
  6. FixedAugust 8, 2026

    Visual regions below the fold are captured instead of failing the check

    A selected area that sits lower down a page than the browser viewport no longer fails its visual check, and an area that straddles the bottom of the viewport is no longer silently cropped to the fold. Region screenshots are now resolved against the full page height rather than the visible window.

    The section picker renders the page in a real browser, shows you a full-page screenshot, and stores the rectangle you drag in full-page CSS pixels — a box 3,000 pixels down a long pricing page is stored at y=3000. The worker then re-renders the page on every check and clips that same rectangle. The clip was being applied without asking for a full-page render, and in that mode a browser resolves the clip against the viewport only, which is 1280x720 by default. Anything starting below 720 pixels intersected nothing.

    That produced two different wrong outcomes depending on where your box was. A region entirely below the fold threw "clipped area is either empty or outside the resulting image", every region on the monitor failed the same way, and the run reported that no screenshot could be captured while the text check beside it carried on working. A region that started above the fold and continued past it was worse, because it never errored: the capture came back cropped at the 720-pixel line, so the monitor quietly pixel-diffed a fraction of the area you selected and would never see a change in the part that got cut off.

    Region clips now render against the full document height, so the captured area matches the rectangle you drew regardless of how far down the page it sits. Existing visual monitors need no edit — the stored rectangles were always correct, only the capture was wrong — and the next scheduled check re-captures at the right dimensions. Because the previous run's screenshot for a truncated region has different dimensions from the corrected one, the first check after this change reports the region as changed on a dimension change, then settles.

    The failure message was misleading too. "All region selectors failed to resolve" was written for element-anchored regions and told you nothing when your regions were drag-selected rectangles, which is what the picker produces. Failed checks now name each region and the actual reason it produced nothing — a selector that matched no element, a capture that timed out, or a clip the browser rejected.

    Those messages are now visible where you would look for them. A run whose capture failed stores no screenshot, so it never appeared in the Visual tab's history at all, and the tab fell back to "No screenshots yet - the first comparison appears after two checks". That is the same thing a healthy monitor says before its second check, so a monitor failing every single capture was indistinguishable from one that had simply just started, and the advice was to wait. The Visual tab and the visual monitors list now show the actual capture error whenever the most recent check failed, and when older screenshots do exist the error sits above them rather than letting a stale image read as current. A failure that has since recovered is not shown - only one newer than the last good capture.

    What was affected
    Visual monitors with one or more drag-selected regions whose rectangle started below 720 CSS pixels, or crossed that line, at the default viewport.
    Not affected
    Whole-page visual monitors (no regions selected), element-anchored regions, and the text extraction pipeline, which runs independently of the screenshot step.
    Viewport
    Still 1280x720 unless overridden per monitor via `visual_config.viewport` (320-3840 wide, 240-2160 tall). Region rectangles are stored in full-page CSS pixels relative to that width.
    Error reporting
    A run with no usable screenshot now reads "No screenshot captured — every selected area failed" followed by the per-region reason, instead of a fixed selector-resolution message.
    Where failures show up
    The monitor's Visual tab and its card on /visual, whenever the latest check failed to capture. Text extraction on the same monitor is unaffected and keeps running.
  7. NewAugust 1, 2026

    Lite plan: 25 monitors on a two-hour check interval

    Lite is a new paid tier between the permanently free plan and Starter, giving 25 monitors, a two-hour minimum check interval, 90-day run retention, and 250 AI extraction calls a month.

    The gap between Free and Starter was too wide. Free is genuinely usable - 5 monitors, the full extraction and condition loop, signed webhooks, no time limit - but its minimum interval is 24 hours, which is fine for a competitor pricing page and useless for anything that moves during a working day. Starter, at 50 monitors and hourly checks, was more than a lot of people needed to cross that line.

    Lite sits in between. 25 monitors, a 7,200-second (two-hour) floor on the check interval, 90 days of run history instead of 14, two delivery destinations per monitor instead of one, 250 prompt-based extraction calls a month, and 30 manual "run now" triggers a day. Visual monitoring is included at 3 concurrent visual monitors with a 1 GB monthly screenshot budget.

    The residential proxy network stays a Starter-and-up feature on purpose: proxy bandwidth is metered in bytes and priced accordingly, so bundling it into the cheapest paid tier would either subsidise heavy users or force the price up for everyone. Every other part of the product - all six extraction methods, the full condition builder, webhook signing, Slack, Discord, and email delivery - is available on Lite.

    The limits themselves are enforced server-side at every write site rather than in the interface, so an API client hits the same ceiling as the dashboard and gets a 429 with the limit that was exceeded named in the response.

    Monitors
    25 (Free: 5, Starter: 50, Pro: 250, Scale: 1,500).
    Minimum interval
    2 hours (Free: 24 hours, Starter: 1 hour, Pro: 15 minutes, Scale: 5 minutes).
    Run retention
    90 days (Free: 14 days).
    Delivery destinations
    2 per monitor (Free: 1).
    AI extraction
    250 prompt-based extraction calls per month.
    Visual monitoring
    3 concurrent visual monitors, 1 GB of screenshots per month.
July 2026
  1. NewJuly 16, 2026

    Visual monitoring: region-scoped screenshot diffs, end to end

    Visual monitoring shipped as a complete layer - dashboard section, REST API, and documentation. Any monitor can now screenshot the page on every check and pixel-diff it against the previous run, scoped to the regions you care about.

    Text extraction tells you that a price field changed from 49 to 59. It tells you nothing about a banner that vanished, a layout that broke after a deploy, a checkout button that moved below the fold, or a competitor quietly redesigning their pricing table. Visual monitoring covers that class of change: it captures the rendered page and compares it pixel by pixel against the last capture.

    It is an additive layer, not a separate monitor type. Turn it on alongside whatever extraction method a monitor already uses, and each check produces both the extracted fields and a screenshot diff. The two have independent triggers: the field-level condition fires on your diff predicate, and the visual layer fires on its own changed-pixel threshold, expressed as a percentage of the compared area and defaulting to 1%.

    Scoping is what makes it usable on real pages. An unscoped full-page diff on any site with a rotating hero, a live visitor counter, or an ad slot fires on every single run. So regions are first-class: you can pin up to 20 regions per monitor, each either element-anchored to a CSS selector - re-resolved on every run, so it survives reflow and content shifts - or a fixed rectangle in full-page CSS pixels, drag-selected in the region picker. You can also list selectors to hide before the shot is taken, which is the clean way to deal with cookie banners and chat widgets.

    Screenshots are always browser-rendered and cost storage and egress, so visual monitors are metered separately from ordinary ones. The default viewport is 1280x720 and full-page capture (full scroll height) is on by default; both are overridable per monitor, with viewport width accepted between 320 and 3,840 pixels.

    Trigger
    Changed-pixel percentage threshold, 0-100, default 1%.
    Regions
    Up to 20 per monitor. Each is either a CSS selector (re-resolved every run) or a fixed x/y/width/height rectangle - exactly one of the two.
    Exclusions
    Up to 20 `ignoreSelectors`, hidden before the screenshot is taken.
    Capture
    Full scroll height by default; viewport default 1280x720, overridable 320-3840px wide.
    Per-tier caps
    Concurrent visual monitors: Free 1, Lite 3, Starter 10, Pro 75, Scale 500. Monthly screenshot storage: 200 MB, 1 GB, 5 GB, 50 GB, 500 GB.
  2. NewJuly 5, 2026

    The playground, and a side-by-side visual diff viewer

    The playground lets you run a real extraction against a real URL with no account, and the new diff viewer shows two page snapshots side by side or as an overlay with the changed regions highlighted.

    Change detection is hard to evaluate from a feature list. Whether a CSS selector holds up against the page you actually care about is an empirical question, and asking someone to create an account to find out is asking them to spend trust before they have any. The playground removes that step: paste a URL, pick an extraction method, see the extracted fields.

    The diff viewer is the other half of the same problem. Once you have two captures of a page, the question is what changed, and a unified text diff is a poor answer for anything visual. The viewer offers side-by-side comparison and an overlay mode with changed regions highlighted - the presentation people already know from changedetection.io, because familiarity is worth more here than novelty.

    The playground also grew the content around it that makes it self-serve: a step-by-step walkthrough of setting up a first monitor, an FAQ covering the questions that come up before signup, and a live visual diff demo you can drive without configuring anything. It is linked from the navigation, the footer, and the sitemap.

    One caveat worth stating plainly: the playground is a single synchronous extraction, not a monitor. It does not schedule, diff against history, evaluate conditions, or deliver anything. It exists to answer "can Verid read this page", which is the only question worth answering before signup.

June 2026
  1. NewJune 30, 2026

    Screenshot capture on every run

    Monitors can now capture a full-page screenshot on each check, stored alongside the extracted fields, giving every run a visual record instead of only a set of values.

    This is the foundation the visual monitoring layer was later built on, and it is useful on its own. When a monitor reports that a field went from present to null, the first question is always what the page looked like at that moment - whether it was a layout change, a bot wall, a cookie interstitial, or a genuinely empty value. Without a capture, that question is unanswerable after the fact.

    Screenshots attach to the run record, so the run history becomes a visual timeline of the page rather than a column of extracted values. That also makes selector breakage diagnosable: if a CSS selector stops matching, the screenshot from the failing run usually shows why in a second.

    Captures are browser-rendered, which means a monitor with screenshots enabled always takes the browser path rather than the cheap static fetch. Storage is metered per month per plan.

  2. ImprovedJune 19, 2026

    AI extraction: retries with a fallback model provider

    Prompt-based extraction now retries transient provider failures with backoff and falls back to a second model provider when the primary one is unavailable, so an upstream LLM outage no longer fails the run.

    Prompt-based extraction is the method you reach for when a page has no stable selector to hold onto - inconsistent markup, values buried in prose, or a layout that changes often enough that CSS selectors are not worth maintaining. Its weakness was that it inherited the availability of a single model provider. A rate limit or a provider incident turned into a failed run, and enough consecutive failed runs auto-paused the monitor.

    The extractor now treats provider errors as what they usually are - transient - and retries them with exponential backoff before giving up. If the primary provider stays unavailable, it fails over to a second provider rather than failing the run. From the monitor's point of view nothing changed except that a class of failure stopped happening.

    The same release closed the loop on auto-pausing. A monitor that fails repeatedly is still paused, because hammering a broken endpoint on a schedule helps nobody, but you now get an email when it happens. Before this, a monitor could go quiet and stay quiet, and silence is indistinguishable from "nothing changed" - which is the worst possible failure mode for a monitoring product.

    AI extraction calls are metered per month because they carry a real per-call cost, and the retry path is careful not to bill a retry as a fresh call against your quota.

    Retry policy
    Exponential backoff on transient provider errors, then failover to a second provider.
    Monthly AI call quota
    Free 50, Lite 250, Starter 500, Pro 5,000, Scale 25,000.
    Auto-pause
    Still triggered by repeated failures, now always accompanied by an email.
  3. ImprovedJune 7, 2026

    Paginated run and delivery history

    The runs and deliveries views now paginate, so accounts with long retention windows and hundreds of monitors load in a bounded amount of time instead of fetching every historical record.

    Retention runs from 14 days on Free to 730 days on Scale, and a Scale account can hold 1,500 monitors checking as often as every five minutes. Loading that history unpaginated was fine in the first month and progressively less fine after that - the page got slower for exactly the accounts that used the product most.

    Both history views now page through results server-side. The practical effect is that the views stay responsive regardless of how much history an account has accumulated, and that the underlying queries stay bounded rather than growing with account age.

    Filtering and inspection behaviour is unchanged: you can still narrow to a single monitor, a status, or a delivery destination, and open any individual run to see the extracted values, the condition evaluation, and the resulting delivery attempts.

    Retention by plan
    Free 14 days, Lite 90, Starter 180, Pro 365, Scale 730.
May 2026
  1. ImprovedMay 30, 2026

    Automatic browser fallback when a static fetch fails

    A static HTTP fetch that dies with a network error now escalates to the headless browser instead of failing the run, so transient connection problems and fetch-hostile servers stop producing empty runs.

    Verid fetches in layers, cheapest first: a plain HTTP request, then a headless browser render, then the browser routed through a residential proxy network. The escalation was already in place for the cases you would expect - a monitor configured to need JavaScript, a page returning a bot wall. What it did not cover was the plain network error: a connection reset, a TLS handshake failure, a server that hangs up on a request whose fingerprint it dislikes. Those failed the run outright even though the browser path would have succeeded.

    Now a network-level failure on the static path escalates rather than terminating. This matters most for the long tail of sites that are not deliberately bot-protected but are unfriendly to anything that is not a browser, which in practice is a large share of the web.

    The same release fixed a genuine correctness bug on the delivery side: deliveries that had already been queued before a monitor was paused were still being sent afterwards. Pausing a monitor is a request to stop hearing from it, so the pause now applies to work already in the queue, not just to future checks.

    Fetch layers
    Static HTTP → headless browser → browser via residential proxy, escalating only as needed.
    Proxy bandwidth by plan
    Free and Lite: none. Starter 50 MB/month, Pro 500 MB, Scale 5 GB.
  2. NewMay 17, 2026

    Side-by-side change comparison

    The change detail view now shows the previous and current extracted values next to each other, field by field, instead of collapsing both into a single merged diff.

    A merged inline diff is the right presentation for source code and the wrong one for a set of extracted fields. When a monitor pulls eight fields off a product page and two of them moved, what you want is to read the old record against the new record and see which pairs disagree - not to reconstruct two states from a stream of additions and deletions.

    The comparison view puts previous and current side by side with the changed fields marked, so the answer to "what actually changed" is available without reading. It also makes an easily-missed case obvious: a field that went from a value to null, which usually means selector breakage or a bot wall rather than a real change on the page.

    This view is what the webhook payload describes in JSON, so the dashboard and the API now agree on shape as well as content - a useful property when you are debugging why a condition fired.

  3. NewMay 5, 2026

    Node.js SDK published to npm as @verid.dev/sdk

    The official Node.js SDK is on npm as @verid.dev/sdk - a standalone, MIT-licensed client for the Verid REST API with typed monitor, run, and delivery models.

    The REST API was always the primary interface, but "call the REST API" still means writing a client: request signing, typed response models, pagination, error handling. The SDK is that client, maintained alongside the API rather than reverse-engineered from it.

    It is deliberately standalone. It imports nothing from the internal packages that make up the rest of the product, which means the published surface is exactly what is documented and cannot drift with an internal refactor. It ships MIT-licensed so it can be vendored, forked, or read without any licensing question.

    The typed models cover the objects you actually work with: monitors and their extraction configuration, diff predicates, delivery destinations, runs and their extracted values, and delivery attempts with their status. Because the API is described by a published OpenAPI 3.1 specification, the SDK and the spec are checkable against each other rather than being two independent descriptions of the same thing.

April 2026
  1. NewApril 27, 2026

    llms.txt and raw markdown for AI agents and answer engines

    Verid publishes an llms.txt manifest and a raw markdown view of every documentation and marketing page, so LLM-based agents and answer engines can read the docs without parsing HTML.

    A growing share of the traffic that evaluates a developer tool is not a person with a browser. It is an agent answering "does this product support authenticated JSON API monitoring" on someone's behalf, and it is reading rendered HTML full of navigation, cookie banners, and script tags to find out. That is a lossy way to answer a question that the documentation answers precisely.

    The llms.txt manifest lists the pages worth reading and what each one covers, and every page is additionally available as raw markdown - the same content, no chrome. An agent can fetch the manifest, pick the relevant page, and read prose instead of markup.

    The same release wired up alerting on the dead-letter queue. Delivery jobs that exhaust their retries land in a dead-letter queue rather than disappearing, and that queue now generates an email instead of only being visible to whoever thinks to look at it. A monitoring product that fails silently is worse than no monitoring product, so every terminal failure path now has a notification attached to it.

    Manifest
    /llms.txt, listing every readable page and its subject.
    Raw content
    A markdown view of each documentation and marketing page.
    Dead-letter queue
    Exhausted delivery jobs are retained and now trigger an email alert.
  2. NewApril 23, 2026

    Custom request headers, for monitoring authenticated JSON APIs

    Monitors can send arbitrary request headers, which makes JSONPath extraction work against APIs that require an authorization token, an API key, or explicit content negotiation.

    JSONPath extraction turns Verid into an API monitor rather than only a page monitor: point it at a JSON endpoint, pull specific values out of the response body with a JSONPath expression, and set a condition on those values. Until this shipped, that only worked against public endpoints, because there was no way to authenticate the request.

    Custom headers close that. Send an Authorization bearer token, an X-API-Key, an Accept header to force a JSON representation from an endpoint that content-negotiates, or whatever else the endpoint requires. The practical unlock is monitoring your own infrastructure and your vendors' APIs - a status endpoint, an inventory feed, a rate table, a partner API whose values you depend on - with the same conditions, diffing, and signed delivery as any page monitor.

    Headers are stored per monitor and sent on every check. They are configuration, not secrets management: treat a token stored here the way you would treat a token in any third-party integration, and prefer a scoped, read-only credential.

  3. ImprovedApril 9, 2026

    A browser engine built to survive bot detection

    The headless browser layer was replaced with one hardened against bot detection, raising the success rate on the protected pages that ordinary headless Chrome cannot load at all.

    A large share of the pages people actually want to monitor - retail product pages, airline fares, ticketing, marketplace listings - sit behind bot detection. Plain headless Chrome announces itself through a long list of signals, and the result is not a slightly degraded page but a challenge page or an outright block. No extraction method can recover from that, because the content never arrives.

    The browser layer is now built for this case: it presents as a real browser rather than an automated one, which turns a meaningful set of previously unmonitorable pages into monitorable ones. Combined with the residential proxy layer above it, that covers most of what blocks a monitor from reading a commercially interesting page.

    This stays a layered escalation rather than a default. The cheap static HTTP fetch is still tried first, because most pages do not need a browser and paying browser cost for them is waste; the browser is used when the page needs rendering or the static path is refused; the proxy is used when the browser alone is refused. Proxy bandwidth is metered because it is genuinely expensive, and it is available from the Starter plan up.

    It is worth being straightforward about the limits: this is a best-effort layer against an adversarial one. Some sites will still refuse some requests some of the time, and a monitor that starts returning nulls against a protected page is more likely to be a block than a page change - which is exactly why every run keeps its screenshot.

    Escalation order
    Static HTTP first, headless browser second, browser via residential proxy last.
    Proxy availability
    Starter and above; metered in bytes per month.
March 2026
  1. NewMarch 25, 2026

    28 worked use-case walkthroughs

    A library of 28 use cases, each spelling out the full setup for a real monitoring job: which extraction method to use, what the condition looks like, and where the notification goes.

    The hard part of adopting a change detection API is not the API. It is the modelling: which of the six extraction methods fits this page, what the diff predicate should be so it fires on the change you care about and not on every rotation of the hero image, and which delivery destination makes sense for how urgent the change is.

    Each use case answers those three questions for one concrete job - tracking a product price, watching stock come back in, detecting a competitor's pricing page change, following a search ranking, monitoring a status page, watching a job board or a regulatory filing index. They are written as walkthroughs rather than summaries: the extraction config, the condition, and the delivery setup are all stated explicitly enough to copy.

    They double as the honest answer to "can Verid do X", because a use case that would need a capability Verid does not have is not in the library. The same release added a working contact form backed by a real endpoint, made plan downgrades apply the target plan's caps predictably instead of leaving over-quota resources running, and let accounts created through Google sign-in add a password - previously those accounts had no way to set one, so they had no way to use password reset.

  2. ImprovedMarch 7, 2026

    Authentication rebuilt in-house

    The third-party auth library was replaced with Verid's own session layer: opaque session tokens stored as SHA-256 hashes, scrypt password hashing, and Google OAuth, all on a schema we control.

    The auth library that got the product to its first build was carrying assumptions that did not fit - its own schema conventions, its own session model, and its own opinion about how identity relates to plans and tier limits. Every feature that touched both auth and billing meant working around it.

    The replacement is deliberately unremarkable, which is the point for authentication. A session is a 32-byte opaque token in a cookie; only its SHA-256 hash is stored server-side, so a database disclosure does not hand over usable sessions. Passwords are hashed with scrypt. Google OAuth is supported alongside email and password, and an account can hold both, so signing up with Google and later adding a password works.

    The practical benefit is that user identity, plans, and tier limits now live in one schema with real foreign keys, which is what made per-plan enforcement at every write site possible rather than aspirational. Tier limits are checked server-side on writes, so an API client and the dashboard hit the same ceiling.

    Sessions
    32-byte opaque token in a cookie; only its SHA-256 hash is persisted.
    Passwords
    scrypt.
    Providers
    Email and password, and Google OAuth, on the same account.
February 2026
  1. FixedFebruary 22, 2026

    Scheduler race, browser pool, and graceful shutdown

    Three reliability defects in the background worker were fixed: a scheduler race that could enqueue the same monitor twice, unbounded browser spawning, and a shutdown path that dropped in-flight jobs.

    The worker is the part of Verid that actually runs your monitors, so its failure modes are the ones that produce wrong output rather than a broken page. All three of these were in that category.

    The scheduler race could enqueue one monitor twice for the same interval. The visible consequence was a duplicate check, which meant a duplicate diff against the same previous state and, in the worst case, a duplicate notification for a single change. The scheduler now claims work in a way that makes double-enqueue impossible rather than merely unlikely.

    Browser instances were spawned without a bound, so a burst of monitors that all needed rendering at the same moment competed for memory until the worker was starved. Browser rendering now goes through a fixed-size pool, which trades a little queueing under load for not falling over under it.

    Shutdown discarded whatever was in flight. Since deploys are routine, that meant a small number of checks were silently lost on every deploy - and a lost check is invisible, which is the problem. Shutdown now drains in-flight jobs before exiting.

  2. NewFebruary 14, 2026

    Signed webhooks, Slack, Discord, and email, per monitor

    Delivery became a first-class part of a monitor: configure webhook, Slack, Discord, and email destinations directly on each monitor, with HMAC-SHA256 request signing, automatic retries, and a dead-letter queue for terminal failures.

    Detecting a change is only half the product. The other half is getting it somewhere useful with enough reliability that you can build on it, which means signing, retries, and a defined behaviour when delivery ultimately fails.

    Webhook requests are signed with HMAC-SHA256 in the Stripe and Svix format: a `Verid-Signature` header carrying `t=<unix timestamp>,v1=<hex hmac>`, where the signed payload includes the timestamp so a captured request cannot be replayed later. If you have verified a Stripe webhook before, the verification code is the code you already know. Each monitor has its own signing secret, stored encrypted at rest.

    Failed deliveries retry three times with exponential backoff starting at five seconds. A delivery that exhausts its retries lands in a dead-letter queue rather than vanishing, and every attempt is recorded with its status - pending, success, failed, or dead - so the deliveries view answers "did it arrive" without guesswork.

    Slack, Discord, and email destinations exist because not every change needs a webhook endpoint. The number of destinations you can attach to one monitor is plan-dependent, which is what makes fan-out - webhook to your service, Slack to the team channel, email to a stakeholder - a paid-plan capability. The same release added a notification section to the monitor page itself, so delivery is configured where the monitor is rather than in a separate area.

    Channels
    Webhook, Slack, Discord, email.
    Signature header
    `Verid-Signature: t=<timestamp>,v1=<hex HMAC-SHA256>` - Stripe-compatible.
    Retries
    3 attempts, exponential backoff from 5 seconds, then the dead-letter queue.
    Attempt statuses
    pending, success, failed, dead - all recorded per attempt.
    Destinations per monitor
    Free 1, Lite 2, Starter 3, Pro 10, Scale 25.
  3. FixedFebruary 3, 2026

    Browser rendering was silently falling back to a plain HTTP fetch

    Monitors configured to render in a headless browser were in fact being fetched over plain HTTP, so any page that builds its content with JavaScript extracted nothing while reporting a successful run.

    This is the worst shape a bug can take in a monitoring product: it produced a confident wrong answer. A monitor set to browser rendering ran, reported success, and extracted nothing - because it had fetched the server-rendered shell of a client-rendered page, which contains no content to extract.

    For a single-page application the visible symptom was empty or null fields on every run, which reads as "the selector is wrong" rather than "the page was never rendered". Anyone debugging it would have spent their time on the selector, which was fine.

    The browser path is now actually taken when a monitor asks for it. If you evaluated Verid before this date against a JavaScript-rendered page and concluded it could not read the page, that conclusion was about this bug, not about the product.

January 2026
  1. NewJanuary 21, 2026

    A guide for every extraction method

    Step-by-step guides for all six extraction methods - CSS selectors, XPath, JSONPath, regular expressions, full-page hashing, and AI prompt extraction - each with example input markup and the exact JSON it produces.

    Choosing an extraction method is the first decision when setting up a monitor and the one most likely to be made wrong. CSS selectors are the right default and break on markup churn. XPath handles structural relationships that CSS cannot express, like selecting a cell by the text of its sibling. JSONPath is for API responses. Regular expressions are for values embedded in prose or inline script. Full-page hashing answers "did anything at all change" without naming any field. AI prompt extraction is for pages with no stable structure to target.

    Each guide covers one method with real input and real output: the HTML or JSON going in, the expression, and the exact extracted result. They are written to be worked through rather than skimmed, because the failure mode here is a plausible-looking selector that silently matches the wrong node.

    The guides also state where each method breaks down, which is the part usually left out. Knowing that a CSS selector anchored on a generated class name will not survive the next deploy is more useful than another example of a selector that works.

  2. NewJanuary 18, 2026

    A published OpenAPI 3.1 specification for the REST API

    The Verid REST API is described by a published OpenAPI 3.1 specification, so clients, types, and documentation can be generated from a single machine-readable contract.

    An API without a machine-readable specification is an API you have to read prose to use, and one where the documentation and the implementation drift apart quietly. The spec makes the contract explicit: every endpoint, every request and response schema, every error shape.

    Practically, that means you can generate a typed client in your language rather than hand-writing request code, load the spec into an HTTP client or an API gateway, and diff it between versions to see exactly what changed. It is also what the official Node.js SDK is checked against, so the SDK and the API cannot silently disagree.

    The endpoints it covers are the ones the dashboard itself uses: creating and updating monitors with their extraction configuration and diff predicates, managing delivery destinations, and reading run history with extracted values and delivery outcomes. There is no private API behind the public one.

5 monitors, all six extraction methods, the full condition builder, and signed webhooks. No credit card, no time limit.