/** * AUTO-GENERATED — do not edit by hand. * Source: src/resources/troubleshooting-skill.md (the single source, distilled * from validated support resolutions). * Regenerate: `npm run gen-troubleshooting-guide` (runs automatically on `prebuild`). * * The troubleshooting guide exposed as an MCP resource + the backing CONTENT * for the `get_troubleshooting_guide` tool. */ export declare const TROUBLESHOOTING_GUIDE_URI = "sealmetrics://troubleshooting-guide"; export declare const TROUBLESHOOTING_GUIDE_NAME = "SealMetrics Troubleshooting Guide"; export declare const TROUBLESHOOTING_GUIDE_DESCRIPTION = "Validated symptom\u2192cause\u2192fix answers for the most common SealMetrics setup and data questions - tags firing before the tracker loads, lost conversions/microconversions, duplicate pageviews in SPAs or Tag Manager setups, channel rules that never match (traffic stuck in Unassigned), payment-gateway and login/SSO domains showing up as traffic sources, numbers that do not match another analytics tool, and live browser checks of whether the tag actually loads on a page."; export declare const TROUBLESHOOTING_GUIDE_CONTENT = "# SealMetrics Troubleshooting Guide\n\nYou are helping a SealMetrics user diagnose a setup or data problem. This guide\ncontains **validated support resolutions** \u2014 each entry is a real, recurring\nquestion answered symptom \u2192 cause \u2192 fix. Do not guess beyond it: follow the\nmatching entry, use the verification tools it lists to confirm with the user's\nreal data, and if nothing matches, say so and suggest contacting SealMetrics\nsupport (see the last section for what to include).\n\nFind the symptom first:\n\n| Symptom | Section |\n|---------|---------|\n| Console error `sealmetrics is not defined`, conversions/micros missing, events fired from GTM | 1 |\n| Pageviews or entrances look inflated (~2x) on a SPA, or content grouping missing on SPA navigations | 2 |\n| A channel rule is active but traffic still shows as Unassigned (or the wrong channel wins) | 3 |\n| Payment gateway (Redsys, etc.) shows up as a traffic source, or \"how do I bypass the gateway referrer?\" | 4 |\n| An excluded login / SSO domain still appears as a top traffic source | 4 |\n| SealMetrics numbers don't match GA4 / the e-commerce platform / another tool | 5 |\n| No data at all, or the pixel \"is not detected\" | 6 |\n| Need to check live whether the tag loads on a page, or why a URL is never measured | 7 |\n\n---\n\n## 1. Tags fire before the tracker loads (\"sealmetrics is not defined\")\n\n**Symptom**: console error `sealmetrics is not defined` (or\n`sealmetrics.micro is not a function`); conversions or microconversions never\narrive. Almost always a Tag Manager setup where an event tag fires on an early\ntrigger (e.g. before the page \u2014 or the tag loading the tracker \u2014 has run), or a\ntag injected via `document.write`.\n\n**Cause**: the SealMetrics tracker exposes one global API (`window.sealmetrics`)\nand event tags call it (`sealmetrics.micro(...)`, `sealmetrics.conv(...)`). If\nan event tag runs **before** the tracker script (`t.js`) has executed, the API\ndoes not exist yet \u2014 the call throws and that event is lost. (In SealMetrics\nv1 each event tag was autonomous and built its own request, so v1 setups never\nhit this; it surfaces when migrating to v2.)\n\n**Fix \u2014 the buffer stub (canonical answer).** Paste this tiny inline snippet so\nit runs **before everything else** \u2014 directly in the ``, or as a GTM\nCustom HTML tag on the **Initialization \u2013 All Pages** trigger:\n\n```html\n\n```\n\nThe stub creates a queue: any `sealmetrics.micro(...)` / `sealmetrics.conv(...)`\ncall that fires before the tracker loads is buffered and replayed automatically,\nin order, the moment the real library starts. Result: no console errors, no\nlost events, and a single pageview (the tracker still loads exactly once).\n\nKey points when recommending it:\n\n- **No other change is needed.** Event tags keep calling\n `sealmetrics.micro(...)` exactly as they do now; the loader tag keeps its\n current trigger. The only addition is the stub, first of all.\n- **The stub is idempotent** (pasting it twice is a no-op thanks to the `||`\n guards) and weighs ~180 bytes gzipped.\n- **Payloads are built at call time** (URL, referrer, etc. are read live when\n the call happens), so events do not depend on any earlier initialization.\n- **Reject the two tempting alternatives**: (a) moving the *whole tracker* to an\n Initialization trigger is unnecessary \u2014 the stub already solves the ordering;\n (b) **tag sequencing** (firing the loader before every event tag) reloads the\n tracker several times per page and inflates pageviews. The stub avoids both.\n- **If a pageview is queued through the stub** (i.e. the site fires\n `sealmetrics({...})` manually), the tracker must be loaded with `?auto=0`,\n otherwise the drained pageview AND the automatic one both fire \u2192 duplicates.\n Canonical stub pattern: **stub + `t.js?...&auto=0`**. If only `micro`/`conv`\n calls go through the stub, the `auto` setting does not need to change.\n- **Iframe exception**: never inject the stub inside a sandboxed iframe (e.g.\n Shopify Web Pixels) \u2014 there, call `sealmetrics.conv()` / `sealmetrics.micro()`\n directly.\n- **Always use the `sealmetrics` global**, never the `sm` / `_sm` shorthands.\n Those two are best-effort aliases: the tracker only claims them if they are\n free, so during a v1\u2192v2 migration (v1 also registers `window.sm`) a tag\n calling `sm(...)` may silently hit the wrong tracker. Double-tagging v1+v2\n during a migration is safe as long as v2 tags call `sealmetrics(...)`.\n\n**Verify**: reload the page with `?debug=1` appended to the URL (events are\nlogged to the console), then confirm the event reached the backend with\n`verify_event_instrumented`, or check `get_microconversions_raw` /\n`get_conversions_raw` for the expected event.\n\n---\n\n## 2. Duplicate pageviews on SPAs / content grouping missing on SPA navigations\n\n**Symptom**: pageviews (and often entrances) roughly double on a single-page\napp; typically the site fires its own \"virtual pageview\" tags from GTM (History\nChange / virtualPageView triggers). Or: content grouping is set on manual\npageviews but SPA navigations still produce hits without it.\n\n**Cause**: the tracker natively auto-fires a pageview on **every SPA\nnavigation** (History API `pushState` / `replaceState` / `popstate`).\n`?auto=0` does **not** disable that \u2014 it only gates the *initial* pageview;\nthe SPA listeners stay active. So a site that also fires manual pageviews\ncounts every navigation twice: the automatic hit (no/static grouping) plus the\nmanual one (correct grouping).\n\n**Fix**: load the tracker with **`&spa=0`** to suppress the automatic SPA\npageview and keep only the manual ones.\n\n| `?spa=` value | Automatic pageview on SPA navigation |\n|---------------|--------------------------------------|\n| missing / empty / `1` | ON (default) |\n| `0` | OFF (manual SPA mode) |\n\n`auto` and `spa` are independent flags (all four combinations are valid). The\n**canonical Tag Manager pattern for full manual instrumentation with per-page\ncontent grouping** is:\n\n```\nstub (section 1) + t.js?id=...&auto=0&spa=0 + one sealmetrics({ group: '...' }) per page shown\n```\n\nYou fire the initial pageview and every SPA pageview yourself, exactly once\neach, always with the right grouping.\n\nCommon misconceptions to correct:\n\n- *\"The script fires the pageview and my tag fills in the group afterwards.\"*\n No \u2014 **every `sealmetrics({ group })` call IS a complete pageview hit** with\n its own content grouping. Nothing is enriched retroactively. That is exactly\n why auto + manual = double counting.\n- Automatic pageviews can only carry the **static** group from the script URL\n (`t.js?group=...`); they can never pick up a per-page group computed at\n runtime. Dynamic grouping requires manual pageviews (and therefore `spa=0`).\n- Keeping the loader and the pageview/event calls in **separate tags is fully\n supported** \u2014 duplication never comes from splitting tags; it comes from\n automatic and manual pageviews both firing.\n- **Timing**: fire the manual pageview **after** the URL change (the tracker\n updates its internal URL/referrer state on the History event itself). GTM's\n History Change trigger already fires after the change, so it is safe there.\n\n**Before enabling `spa=0`, check coverage**: with it, anything your own\ntriggers don't cover stops being measured \u2014 typical gaps are hash-only routing\n(`#/...`), AJAX checkout steps, and filter/pagination navigations. Extend the\ntriggers first, then flip `spa=0`.\n\n**Verify**: browse the SPA with `?debug=1` and count one pageview per\nnavigation; then compare pageview counts in `get_overview` before/after.\n\n---\n\n## 3. Channel rules that never match (traffic stuck in Unassigned)\n\n**Symptom**: a custom channel rule is active, priority looks right, but the\nmatching traffic still shows as Unassigned \u2014 or a lower-priority rule \"wins\".\nThis is almost never a publication or priority bug; it is nearly always one of\ntwo pattern-writing traps.\n\nHow matching actually works (same engine in the pixel, the dashboard tester and\nthe MCP tools):\n\n- Patterns are **RE2 regexes** (Go dialect \u2014 no lookahead/lookbehind, no\n backreferences), matched **unanchored** (substring search). An empty pattern\n is a wildcard.\n- The incoming `source` / `medium` / `campaign` values are **lowercased and\n trimmed** before matching, but **patterns are applied exactly as written**.\n- All non-empty patterns of a rule must match at once (AND).\n- Evaluation order: your account's custom rules first (by priority, 0\u20131000,\n highest first), then the built-in defaults. **The first rule that matches\n wins**; if none does, the hit is Unassigned.\n\nThe two traps:\n\n1. **Any uppercase letter in a pattern makes the rule dead.** The traffic\n arrives lowercased (`fb-sitelink`, `tradedoubler`, `app_phg`), so a pattern\n like `^FB-SiteLink$` or `(ADS|PaidSocial)` can never match \u2014 silently: the\n rule stays active, no warning is shown, and the traffic falls through to the\n next rule or Unassigned. **Write patterns entirely in lowercase.** (This\n also stays correct if pattern matching becomes case-insensitive in a future\n release.)\n2. **An unescaped `.` is \"any character\", not a dot.** A pattern like\n `(.brand)` requires *some* character before \"brand\", so the bare value\n `brand.com` will not match it. Since matching is already substring-based,\n prefer plain alternatives like `(brand|otherbrand)` over\n `^.*(.brand).*$`; escape real dots as `\\.` (e.g. `facebook\\.com`).\n\nOperational facts to set expectations:\n\n- Changes to rules propagate in **~5 minutes** (pixel cache refresh).\n- Classification happens **at collection time**: rule changes apply to **new\n traffic only** \u2014 historical hits are never reclassified.\n- The dashboard's **\"Test a visit\"** (Site settings \u2192 Channels) and the MCP\n tool `test_channel_rules` use the exact same engine as live classification \u2014\n always test the literal source/medium/campaign values before concluding a\n rule is broken. Pull real values to test with from `get_traffic_sources`.\n- CSV import in the dashboard is an **atomic replace-all** of the account's\n custom rules \u2014 never import an edited export without checking that no rules\n were added after that export. (The MCP's `import_channel_rules` is safer:\n drafts-only and dry-run by default.)\n- Via MCP, `create_channel_rule` / `update_channel_rule` only touch **inactive\n drafts** \u2014 publishing a rule is always a human action in the dashboard.\n\n**Diagnosis workflow**: `list_channel_rules` \u2192 spot uppercase letters or\nunescaped dots in the failing patterns \u2192 `test_channel_rules` with the exact\nreal values \u2192 fix the pattern (all lowercase) \u2192 test again \u2192 publish in the\ndashboard \u2192 confirm on new traffic after ~5 minutes with `get_channels` or\n`get_top_channels`.\n\n---\n\n## 4. Payment gateways (Redsys, etc.) and referrer attribution\n\n**Symptom**: the user wants to \"add the payment gateway as a passthrough\nreferrer\" so the sale is not attributed to the gateway \u2014 or asks why a small\n`redsys / payment` (or similar) row exists in their sources.\n\n**Answer**: common payment gateways (e.g. Redsys: `sis.redsys.es` and\nvariants) are already recognized **globally, out of the box** \u2014 there is\nnothing to configure. When the visitor returns from the gateway within a live\nsession, the visit keeps its **original attribution** (the conversion is\ncredited to the channel that brought the user; the gateway never appears as a\nsource).\n\nA small residual ` / payment` row can still appear: those are\nsessions that **expired while the user was at the gateway** (a new entrance is\ncreated on return). This is expected and marginal \u2014 not a misconfiguration.\n\n**When the excluded domain is a login / SSO domain, that residual is not\nmarginal.** A payment gateway is visited mid-session, so the session is usually\nstill alive on return and the leftover row stays small. A login domain sits at\nthe **entrance** of the application instead: visitors who open the app directly\n(bookmark, email link, typed URL) have no earlier session to preserve \u2014 the\nreturn from the login is the first hit that can be recorded \u2014 so those visits\nlegitimately become new entrances carrying the fallback source configured for\nthat domain. On a property whose traffic is mostly returning logins, that row\ncan be one of the largest in the Sources report.\n\nThat is expected, and it does not mean the exclusion failed: **the exclusion\npreserves an existing session, it cannot recreate one that never existed.**\nSealMetrics is cookieless, so there is no persistent visitor id linking today's\nlogin to an acquisition weeks ago. Sessions that *do* reach the login with a\nlive session keep their original source normally \u2014 those are the two branches of\nthe same rule, and only the second one is visible as a separate row.\n\n**How to confirm**: call `get_top_landing_pages`. If the post-login callback\npage dominates the entrances, the property is measuring logins rather than\nacquisition, and the row is expected. Note also that when the application\nredirects unauthenticated visitors to the login with **server-side redirects**,\nno page is ever served on the app domain before the login: the tracker cannot\nrun there, so nothing is missing from the instrumentation and adding the pixel\n\"earlier\" would change nothing. Section 7 shows how to verify that redirect\nchain in the browser.\n\nCustom referrer mappings beyond the built-ins (e.g. an unrecognized local\ngateway or SSO domain) are currently **enabled by SealMetrics support on\nrequest** \u2014 tell the user to contact support with the domain and the\nsource/medium they want it mapped to.\n\n---\n\n## 5. \"SealMetrics doesn't match GA4 / my platform / my other tool\"\n\nNever assume a bug from a totals gap. Reconcile methodically \u2014 most gaps are\ndefinitional, and several tools being compared undercount by design:\n\n1. **Direction check first.** SealMetrics measures **all** visitors (no\n consent banner, no cookies), so on traffic it normally reports **more** than\n consent-gated or cookie-based tools (which lose opted-out users and some\n ad-blocked ones). SealMetrics reporting *lower* traffic than a consent-gated\n tool is a red flag worth investigating; higher is the expected direction.\n2. **Compare definitions, not labels.** An *entrance* (session start) is not a\n GA4 *session*: session windows, timeout rules and attribution models differ\n per tool. Pageviews vs \"events\", first-click vs last-click, view-through \u2014\n align the definition before comparing numbers.\n3. **Align time.** Compare the same timezone and the same day boundaries (the\n site's reporting timezone may differ per tool), and compare **day-by-day\n series, not period totals**: a roughly uniform daily offset means a\n definitional/coverage gap; isolated spikes or cliffs mean an incident\n (deploy, tag change) on a specific date.\n4. **Conversions: reconcile order-by-order, not totals.** Pull the raw list\n with `get_conversions_raw` and match against the platform's order list by\n time/amount. Orders that never render the site's thank-you page \u2014 phone\n orders, subscription renewals, app checkouts \u2014 exist in the platform but\n can never fire web tracking; conversely, page reloads or revisits of the\n confirmation page can inflate the web side if the site lacks its own\n dedup guard.\n5. **Rule out double tagging** (sections 1\u20132) before concluding SealMetrics\n overcounts: duplicated loaders, stub+`auto=1`, or SPA auto+manual pageviews\n are the usual causes of ~2x.\n\n---\n\n## 6. No data at all / \"the pixel is not detected\"\n\n- Confirm the tag actually loads on the page in question: view source or the\n network panel for `t.js`. Common finds: the pixel is only installed on a\n subdomain (e.g. the booking/checkout engine, not the main site), or the tag\n is loaded through a tag manager whose consent settings block it. SealMetrics\n itself needs no consent \u2014 but if the container gates the tag behind consent,\n it will not load for rejecting users.\n- Append `?debug=1` to the URL to see events in the browser console.\n- Use `verify_setup` / `get_setup_status` to check whether the backend has\n received any hit for the site, and `get_overview` for recent traffic.\n- Section 7 shows how to check the page live instead of assuming.\n\n---\n\n## 7. Verifying in the browser (live checks)\n\nSeveral entries above end in \"check whether the tag actually loads on that\npage\". When the user is not sure, verify it instead of assuming it.\n\n**If you have browser tools available** \u2014 a browser-automation MCP server such\nas chrome-devtools, or any equivalent \u2014 run the checks yourself. **If you do\nnot**, either offer the user to install one, or walk them through the same steps\nmanually: open the page in a private window with the browser DevTools Network\npanel open and \"preserve log\" enabled. Over a hosted connector there are no\nbrowser tools available at all; use the manual path there.\n\nBefore running anything, two rules:\n\n- **These checks load the real site and therefore generate real hits in the\n account.** On a low-traffic site, or when repeating a check many times, tell\n the user first.\n- **Only ever run them against a site the user owns.**\n\n**What to look for**\n\n| Check | Where | What it means |\n|-------|-------|---------------|\n| Request to `t.js?id=` | Network panel | The tracker is loaded on that page. Missing \u2192 not installed there, or a tag manager / consent gate is blocking it |\n| `POST` to `/event` answered `204` | Network panel | The hit reached SealMetrics \u2014 `204` is the normal success response, not an error |\n| Status codes of the page itself | Network panel | A chain of `30x` responses with no HTML `200` means **no page is served at all** at that URL. The tracker cannot run, and nothing is missing from the instrumentation. Typical of app URLs that bounce unauthenticated visitors to a login |\n| `document.referrer` on the landing page | Console | Empty on a page reached through a redirect chain started by a bookmark or a typed URL \u2014 which is why such visits are attributed to direct traffic rather than to the redirecting domain |\n\n**Interpreting it**: `t.js` plus a `204` on `/event` means the page is measured\ncorrectly, and any remaining attribution question is about *which session* the\nhit belongs to (see sections 3 and 4), not about the tag. Confirm the backend\nside with `verify_setup` or `get_setup_status`, and the resulting data with\n`get_overview`.\n\n---\n\n## Escalating to SealMetrics support\n\nIf no entry resolves the problem, have the user contact support including:\n\n1. The site URL (and the exact page where it fails).\n2. A screenshot of the browser console error, if any.\n3. **How the tracker is loaded**: tag manager or hardcoded, which trigger fires\n it, and the full script URL including parameters (`id`, `auto`, `spa`,\n `group`).\n4. What was expected vs what the dashboard shows, with the date range."; //# sourceMappingURL=troubleshooting-guide.d.ts.map