SEO Title and Meta Description Drift Detection
Catch silent SEO-critical metadata changes on your own pages or a competitor's - the kind of drift that explains last week's traffic dip.
The scenario
A page on your own site used to rank well for a specific keyword. Last week it dropped two positions. Looking at the page, the title tag now reads slightly differently than what you optimized for - someone edited it during an unrelated content update and didn't realize the title was part of the SEO surface. Or the CMS auto-generated a fresh meta description that's two hundred characters of mush.
For competitive analysts, the same monitor on a competitor's page catches the moment they re-target a keyword.
The problem
Title and meta-description drift is silent. Nothing visibly changes on the page. A site audit catches it monthly, by which point you've already lost two weeks of ranking. No alerting tool focused on SEO routinely watches these fields per-page.
How Verid solves it
Title and meta description are reliable HTML elements with stable selectors. XPath is the cleanest tool for selecting them (since both live inside <head>); regex against the raw HTML is the equally-reliable alternative when you want a single field.
Verid extracts both as fields and fires on any change. Pair it with a list of your top-trafficked URLs and you'll know within minutes when one drifts.
Build the monitor
Extraction config - XPath
{
"method": "xpath",
"fields": {
"title": "//title/text()",
"meta_description": "//meta[@name='description']/@content",
"canonical": "//link[@rel='canonical']/@href",
"og_title": "//meta[@property='og:title']/@content"
}
}
Extraction config - Regex
{
"method": "regex",
"fields": {
"title": "<title[^>]*>([^<]+)</title>",
"meta_description": "<meta\\s+name=\"description\"\\s+content=\"([^\"]+)\""
}
}
Predicate
{ "type": "any_field_changes" }
Or, to fire only on the title (the most ranking-sensitive field):
{ "type": "field_changes", "field": "title" }
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": "SEO - /pricing page",
"url": "https://example.com/pricing",
"schedule_interval_seconds": 21600,
"extract_config": {
"method": "xpath",
"fields": {
"title": "//title/text()",
"meta_description": "//meta[@name=\"description\"]/@content",
"canonical": "//link[@rel=\"canonical\"]/@href",
"og_title": "//meta[@property=\"og:title\"]/@content"
}
},
"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: 'SEO - /pricing page',
url: 'https://example.com/pricing',
schedule_interval_seconds: 21600,
extract_config: {
method: 'xpath',
fields: {
title: '//title/text()',
meta_description: "//meta[@name='description']/@content",
canonical: "//link[@rel='canonical']/@href",
og_title: "//meta[@property='og:title']/@content",
},
},
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-08T08:00:00Z",
"diff": {
"fields_changed": ["title", "meta_description"],
"before": {
"title": "Pricing - Example Product",
"meta_description": "Plans starting at $20/mo. Free tier with 5 monitors.",
"canonical": "https://example.com/pricing",
"og_title": "Pricing - Example Product"
},
"after": {
"title": "Plans & Pricing | Example",
"meta_description": "Choose the plan that fits your team. See features and FAQ.",
"canonical": "https://example.com/pricing",
"og_title": "Plans & Pricing | Example"
}
}
}
The title was rewritten and lost the "Pricing" keyword as the primary token; the meta description swapped a concrete CTA for vague copy. Both worth a conversation with whoever pushed the change.
Caveats & tips
- Cover the URLs that actually matter. Don't try to monitor every URL on a large site - pick the top 20-50 by traffic or revenue contribution and watch those. That's where drift costs money.
- Server-rendered vs client-rendered titles. A React app may have a placeholder
<title>in the HTML that gets replaced by client JS after load. If your XPath extractor returns the placeholder, setfetch_mode: 'browser'so the page renders before extraction. - Canonical drift is its own alert. A change in the canonical URL means Google is being told a different page is the authoritative version - usually a bug worth catching the same day.
- Bulk this with one monitor per page. Verid's tier limits are per-monitor; one per URL keeps diffs clean and alerts targeted.
Related use cases
For full-content competitor copy changes, see competitor ad & landing copy changes. For sitemap-level discovery, see sitemap new URLs. For terms-of-service drift, see terms of service & privacy policy drift.
Related use cases
Ship this monitor today
5 monitors free, no credit card. Set up takes about a minute.
Get started free