← All use cases
Content & MarketingCSSFull Page

SaaS Competitor Pricing and Plan Monitoring

Track when a SaaS competitor changes plan names, tier prices, feature inclusions, or annual discounts - the first signal of a strategic pricing move.

Verid Use Cases·4 min read

The scenario

You sell SaaS, and three competitors share your buyer. When one of them renames a plan, drops the price of their mid-tier, moves a feature from Pro down to Starter, or adds a steeper annual discount, your sales team starts hearing about it in deals - usually after they've already lost a couple.

A SaaS pricing page changes for a reason. A number moving is a tactical discount. A whole-page restructure - new plan names, reshuffled features, seat-based instead of flat pricing - is a strategic repositioning. You want to catch both within hours of them going live.

The problem

A /pricing page is not a clean product feed. It's a marketing surface full of toggles (monthly/annual), live chat widgets, personalized nav, and cookie banners that all change without the price changing. A naive full-page diff fires on every one of those. And the part you actually care about - the price under "Pro" - is often rendered by JavaScript, so a raw-HTML fetch sees an empty shell.

How Verid solves it

Run two monitors against the same page:

  1. A targeted CSS extractor that pins each plan's name, price, and billing period as named fields. The predicate fires only when one of those values changes - that's your actionable, low-noise channel.
  2. A full-page backup that catches structural rewrites the CSS selectors would miss (a redesign changes the selectors, so the targeted monitor goes quiet - the full-page hash still moves).

Both run in browser fetch mode so JavaScript-rendered prices resolve before extraction.

Build the monitor

Extraction config - Targeted CSS

{
  "method": "css",
  "fields": {
    "starter_name": ".pricing-card.starter h3",
    "starter_price": ".pricing-card.starter .price-amount",
    "pro_name": ".pricing-card.pro h3",
    "pro_price": ".pricing-card.pro .price-amount",
    "enterprise_price": ".pricing-card.enterprise .price-amount",
    "annual_discount": ".billing-toggle .annual-savings"
  }
}

Extraction config - Full-page backup

{ "method": "full_page" }

Predicate

For the targeted monitor:

{ "type": "any_field_changes" }

For the full-page backup:

{ "type": "any_field_changes" }

Run them as two monitors with different delivery destinations - the targeted one into your #competitive-intel Slack channel, the full-page one as a quieter "something changed, go look" FYI.

Create the monitor

curl -X POST https://api.verid.dev/v1/monitors \
  -H "Authorization: Bearer vrd_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Competitor pricing - competitor.com/pricing",
    "url": "https://competitor.com/pricing",
    "schedule_interval_seconds": 86400,
    "fetch_mode": "browser",
    "extract_config": {
      "method": "css",
      "fields": {
        "starter_name": ".pricing-card.starter h3",
        "starter_price": ".pricing-card.starter .price-amount",
        "pro_name": ".pricing-card.pro h3",
        "pro_price": ".pricing-card.pro .price-amount",
        "enterprise_price": ".pricing-card.enterprise .price-amount",
        "annual_discount": ".billing-toggle .annual-savings"
      }
    },
    "diff_predicate": { "type": "any_field_changes" },
    "deliveries": [
      { "type": "slack", "webhookUrl": "https://hooks.slack.com/services/..." }
    ]
  }'

SDK:

import { VeridClient } from '@verid.dev/sdk';

const client = new VeridClient({ apiKey: 'vrd_your_api_key' });

await client.monitors.create({
  name: 'Competitor pricing - competitor.com/pricing',
  url: 'https://competitor.com/pricing',
  schedule_interval_seconds: 86400,
  fetch_mode: 'browser',
  extract_config: {
    method: 'css',
    fields: {
      starter_name: '.pricing-card.starter h3',
      starter_price: '.pricing-card.starter .price-amount',
      pro_name: '.pricing-card.pro h3',
      pro_price: '.pricing-card.pro .price-amount',
      enterprise_price: '.pricing-card.enterprise .price-amount',
      annual_discount: '.billing-toggle .annual-savings',
    },
  },
  diff_predicate: { type: 'any_field_changes' },
  deliveries: [
    { type: 'slack', webhookUrl: 'https://hooks.slack.com/services/...' },
  ],
});

What the webhook delivers

{
  "id": "del_01H...",
  "fired_at": "2026-05-08T09:00:00Z",
  "diff": {
    "fields_changed": ["pro_name", "pro_price", "annual_discount"],
    "before": {
      "starter_name": "Starter",
      "starter_price": "$19/mo",
      "pro_name": "Pro",
      "pro_price": "$99/mo",
      "enterprise_price": "Custom",
      "annual_discount": "Save 15% annually"
    },
    "after": {
      "starter_name": "Starter",
      "starter_price": "$19/mo",
      "pro_name": "Growth",
      "pro_price": "$79/mo",
      "enterprise_price": "Custom",
      "annual_discount": "Save 25% annually"
    }
  }
}

That's a repositioning, not a sale: "Pro" became "Growth," the price dropped 20%, and the annual incentive got more aggressive. Your team sees it the morning it ships instead of in a lost deal three weeks later.

Caveats & tips

  • One monitor per pricing surface. The /pricing page, an enterprise quote page, and a regional pricing page each change independently - give each its own monitor.
  • Daily is enough for SaaS. Pricing changes are deliberate and announced internally before they ship. A 24-hour cadence catches everything actionable without burning checks. Move to hourly only around a competitor's known launch window.
  • Browser mode is non-negotiable. Most modern pricing pages render the actual numbers in React. Without fetch_mode: 'browser' you'll extract empty strings.
  • Exclude the billing toggle if it auto-rotates. Some pages animate the monthly/annual price on load. Pin to one billing period's selector, or add the annual figure as its own field so the diff is legible.

Related use cases

For the broader pricing-data play across SKUs, see competitor price tracking. For homepage and campaign copy that signals a launch, see competitor ad and landing copy changes. For the infrastructure tells behind a repositioning, see competitor tech stack changes.

Ship this monitor today

5 monitors free, no credit card. Set up takes about a minute.

Get started free