← writing

Deploying Snitcher Through Matomo Tag Manager

2026-08-02
analyticsmeasurement

Most of the Snitcher documentation assumes you are running Google Tag Manager. Most of the community write-ups assume the same. If you are on Matomo, you are on your own, and the gaps are not obvious until you are three hours into debugging a data layer that was never going to fire.

I recently stood this up on a regulated healthcare property. Here is the version that works, plus the three things that will cost you an afternoon if nobody tells you first.

The short answer

Yes, it deploys through Matomo Tag Manager. There is no native template, so you are writing Custom HTML tags. The architecture is two tags, one custom event, four Data-Layer variables, and four visit-scope custom dimensions in Matomo.

The part that is genuinely different from a GTM build is not the loader. It is getting the company data into Matomo at all, because the reverse-IP lookup resolves after your pageview has already fired.

Trap one: MTM does not read window.dataLayer

Matomo Tag Manager uses window._mtm. If you port a working GTM setup across, your Data-Layer variables will resolve to undefined and you will get no error, no warning, and no indication that anything is wrong. The tags fire. The values are empty.

Every push goes to _mtm:

window._mtm = window._mtm || [];
window._mtm.push({
  event: 'snitcher_identified',
  SnitcherCompanyName: c.name,
  SnitcherCompanyDomain: c.domain,
  SnitcherCompanyIndustry: c.industry,
  SnitcherCompanySize: c.size
});

Your Custom Event trigger then listens for snitcher_identified exactly as it would in GTM.

Trap two: visit scope, not action scope

This is the one that actually matters analytically.

Snitcher's identification is a network round trip. By the time it resolves, Matomo has already sent the pageview. If you create your custom dimensions as action scope, the company data attaches only to the trailing event you fire afterward, and your entry pageview stays blank.

That breaks landing page analysis by company, which for most people is the entire reason they bought the tool.

Create the dimensions as visit scope. Visit scope backfills across the session, so the company name attaches to the whole visit including the pages that loaded before the lookup finished.

Do not solve this by delaying the Matomo pageview until Snitcher resolves. You will lose more sessions to bounce than you gain in attribution, and you will have introduced a third-party dependency into your core analytics load path. Bad trade.

Trap three: two API shapes in the wild

Snitcher's newer Radar loader and the older Spotter snippet return identification data in different shapes, and which one you get depends on the build you are served. The callback path gives you { type, company: { name, domain, industry, employee_range } }. The promise path gives you { success, data: { name, domain, industry, size } }.

Rather than betting on one, normalize both and guard against a double push:

function normalize(id) {
  if (!id) return null;
  if (id.company) {
    if (id.type === 'isp') return null;
    return {
      name: id.company.name,
      domain: id.company.domain,
      industry: id.company.industry,
      size: id.company.employee_range
    };
  }
  if (id.success && id.data) {
    if (id.data.type === 'isp') return null;
    return {
      name: id.data.name,
      domain: id.data.domain,
      industry: id.data.industry,
      size: id.data.size
    };
  }
  return null;
}

Worth noting: getSpotterIdentification is not in the loader's stub method list, so it only exists once the real script has initialized. Call it inside Snitcher.ready() or it will not be there.

Writing to Matomo

If Matomo is deployed through MTM's own Matomo Analytics tag, you cannot always rely on a _paq queue being present. Reach for the tracker instances directly and fall back:

function apply(t) {
  if (name)   t.setCustomDimension(5, name);
  if (domain) t.setCustomDimension(6, domain);
  if (ind)    t.setCustomDimension(7, ind);
  if (size)   t.setCustomDimension(8, size);
  t.trackEvent('Snitcher', 'Company Identified', name || '(unknown)');
}

if (window.Matomo && Matomo.getAsyncTrackers) {
  Matomo.getAsyncTrackers().forEach(apply);
} else {
  var _paq = window._paq = window._paq || [];
  _paq.push(['setCustomDimension', 5, name]);
  _paq.push(['trackEvent', 'Snitcher', 'Company Identified', name]);
}

Swap the indices for whatever Matomo assigned you. They will not be 5 through 8 unless you got lucky.

One cost to be aware of: that trailing event adds a hit to every identified visit, which nudges your actions-per-visit metric. If that number is reported anywhere, either segment the event out or check whether your Matomo version supports a lighter-weight ping.

The part nobody puts in the setup guide

Two defaults deserve a second look before you publish, and both matter more in regulated industries than the docs suggest.

Turn off automatic form tracking. Radar redacts fields it recognizes as sensitive, which means password and credit card. It does not know what an NPI number is. It does not know that a free-text box on a medical information request form might contain an adverse event narrative. On any site handling clinical or professional identifiers, set formTracking: false and instrument the forms you actually want with an explicit allowlist. Letting a third-party processor sweep the DOM is not a decision to make by accepting a default.

Keep the ISP guard. Discarding identifications where type === 'isp' is what keeps residential and mobile carrier lookups out of your dataset. Skip it and you are storing company-shaped noise for every visitor on a home connection, which is both useless and a harder conversation with a privacy reviewer than it needs to be.

And set expectations on the identification rate before anyone sees the first report. If your audience is professionals rather than corporate buyers, a real share of your traffic arrives from home and mobile connections that correctly get discarded. The number will look low against a typical B2B benchmark. That is the guard working, not the implementation failing.

Consent

If your CMP gates the tag manager load itself, you do not need an in-container consent trigger, and that is the cleaner pattern. Nothing in the container can fire early regardless of how someone configures a tag next quarter.

Two things to verify rather than assume. First, which consent category actually gates the load. If your tag manager loads on a performance or analytics grant, confirm that your privacy team classifies reverse-IP firmographic lookup as sitting inside that category rather than under targeting. That is a one-line email now and a remediation project later. Second, check whether the post-consent load holds across geographies. Plenty of deployments run opt-in for EU and UK traffic but notice-only elsewhere, which means the gate may be doing less work than the architecture diagram implies.

Neither blocks the build. Both belong in whatever document describes the implementation, because the answer to "was this consented" should not have to be reconstructed from a container version history eighteen months from now.

machine-readable: markdown · rss · llms.txt
open in: chatgpt · claude · perplexity
more writing ↗