All docs

Pixel-less Mode Setup Guide

Run Attribi without installing the website tracker. Use your CRM as the source of truth and capture click IDs via existing forms.

For customers who want to use Attribi without installing tracker.js.

If your IT team is taking time to approve the tracker install, or you have a no-third-party-JavaScript policy, this guide gets you to near-pixel match rates on ad-platform conversions using only your existing CRM and form code.


What you get without the tracker

When you connect a CRM and one or more ad platforms, Attribi already does the following automatically — no extra setup needed:

  • ✅ Imports leads from your CRM into Attribi
  • ✅ Pushes qualified + closed-won offline conversions to your ad platforms (with hashed email + phone)
  • ✅ Enriches your CRM contacts with attribution data (mta_first_touch_source, mta_attribution_path, etc.)

With the steps in this guide, you also get:

  • Click-level attribution to ad platforms (≈100% match rate vs. ~30-60% for hashed-PII-only)
  • ✅ The "Lead" stage event firing to ad platforms when leads are created in your CRM

The setup takes ~15 minutes and lives entirely on your side — Attribi reads what you populate.


How it works

When someone clicks one of your ads:

  1. They land on your website with a click ID in the URL — ?gclid=ABC123 (Google), ?fbclid=xyz (Meta), etc.
  2. Your existing form on the landing page should capture that click ID and submit it to your CRM as a custom field.
  3. Attribi reads that custom field from your CRM and forwards the click ID to the right ad platform when the lead converts.

You need to populate two custom fields on each new CRM contact:

FieldHolds
mta_clickidThe click ID value (e.g., EAIaIQobChMI...)
mta_clickid_typeThe URL parameter name. One of: gclid, wbraid, gbraid, fbclid, li_fat_id, ttclid, msclkid, ScCid

Attribi already provisioned these fields when you connected your CRM (HubSpot, Salesforce, Pipedrive, Zoho — see your CRM's custom fields panel). For Freshsales and LeadSquared, the fields had to be created manually because their APIs don't allow programmatic creation — see your connection's status in Attribi for details.

The rest of this guide shows how to populate those fields from your form code.


Universal JavaScript snippet — works for any form

The simplest approach: add this script anywhere on your landing pages before your form. It runs once on page load, finds any click ID in the URL, and stores it. When your form is submitted, the script writes the click ID into hidden fields named mta_clickid and mta_clickid_type.

<script>
(function () {
  // Order matters — Google's wbraid/gbraid coexist with gclid in some campaigns,
  // but never overlap with other platforms. The first match wins.
  var paramOrder = ['gclid', 'wbraid', 'gbraid', 'fbclid', 'li_fat_id', 'ttclid', 'msclkid', 'ScCid'];
  var params = new URLSearchParams(window.location.search);

  var found = paramOrder.find(function (p) { return params.get(p); });

  if (found) {
    // Store in localStorage so we can populate the form even if the user
    // navigated to a different page before submitting.
    try {
      localStorage.setItem('mta_clickid', params.get(found));
      localStorage.setItem('mta_clickid_type', found);
    } catch (e) { /* localStorage may be disabled in private mode */ }
  }

  // On form submit, ensure both hidden fields are populated.
  document.addEventListener('submit', function (e) {
    var form = e.target;
    var stored = (function () {
      try {
        return {
          id: localStorage.getItem('mta_clickid'),
          type: localStorage.getItem('mta_clickid_type'),
        };
      } catch (e) { return { id: null, type: null }; }
    })();
    if (!stored.id || !stored.type) return;

    function setHidden(name, value) {
      var existing = form.querySelector('input[name="' + name + '"]');
      if (!existing) {
        existing = document.createElement('input');
        existing.type = 'hidden';
        existing.name = name;
        form.appendChild(existing);
      }
      existing.value = value;
    }

    setHidden('mta_clickid', stored.id);
    setHidden('mta_clickid_type', stored.type);
  }, true);
})();
</script>

This snippet is form-builder-agnostic. It works for HubSpot Forms, Marketo Forms, Pardot, Pipedrive WebForms, custom HTML forms, and any other form that posts standard <input> values to your CRM.

For most customers, this snippet alone is enough. The per-CRM sections below cover form-builder-specific gotchas if you hit any.


Per-CRM specifics

HubSpot Forms (most common B2B path)

HubSpot Forms internally maps form fields to contact properties using the property's internal name. The custom fields Attribi provisioned use these internal names:

  • mta_clickid (text)
  • mta_clickid_type (text)

Steps:

  1. In HubSpot, go to Contacts → Properties and verify the two mta_* properties exist (Attribi provisioned them automatically — they appear in the "Attribi MTA" property group).
  2. Edit your form (or create a new one). Add two hidden contact properties to the form:
    • mta_clickid
    • mta_clickid_type
  3. Embed the form on your landing page. The universal snippet above will populate the hidden fields automatically.

If you use HubSpot's JavaScript embed API (hbspt.forms.create), the hidden fields are populated by the universal snippet. No HubSpot-specific code needed.

Salesforce Web-to-Lead

Salesforce's Web-to-Lead generator outputs HTML forms where field names are auto-generated IDs (e.g., 00N5g0000098xyz) — not your CRM-API field names. To populate MTA_clickid__c:

  1. In Salesforce, go to Setup → Object Manager → Lead → Fields & Relationships. Find MTA_clickid__c and MTA_clickid_type__c (Attribi provisioned them automatically).
  2. Click the field name → copy the auto-generated ID from the URL (after &id=).
  3. In your Web-to-Lead form HTML, add hidden inputs using those IDs as the name:
    <input type="hidden" id="MTA_clickid__c" name="00N5g0000098xyz" value="" />
    <input type="hidden" id="MTA_clickid_type__c" name="00N5g00000ABCDEF" value="" />
    
    Replace 00N5g0... with the actual IDs from step 2.
  4. Adapt the universal snippet to write to those IDs:
    setHidden('00N5g0000098xyz', stored.id);          // mta_clickid
    setHidden('00N5g00000ABCDEF', stored.type);       // mta_clickid_type
    

If your tier supports it, Salesforce Lightning Forms (modern replacement) lets you use API field names directly — much cleaner.

Pipedrive WebForms

Pipedrive's custom fields have hash-ID keys (e.g., f47ac10b58cc4372a567...), not human names. Attribi stored these IDs in your connection metadata — you can find them in the Pipedrive admin Field Manager or by hovering over the field name.

If you use Pipedrive's hosted WebForm: Pipedrive doesn't currently support populating custom fields via URL parameters. Workarounds:

  1. Best: use a tag manager (GTM) to capture click IDs into cookies, and submit a parallel API call to Pipedrive's REST API on form submit. We're investigating a cleaner integration in a future release.
  2. Simplest: skip click ID capture for Pipedrive. Hashed email + phone fallback gives ~30-60% match on ad platforms — good enough for most pixel-less customers.

Zoho CRM

Zoho's custom fields use the literal names (mta_clickid, mta_clickid_type).

Zoho Forms: edit form → add two hidden fields named exactly mta_clickid and mta_clickid_type. The universal snippet populates them.

Custom forms posting to Zoho: include mta_clickid and mta_clickid_type as form field names. They map directly to the Zoho contact module fields.

Freshsales

Freshsales prefixes custom fields with cf_. The fields are:

  • cf_mta_clickid
  • cf_mta_clickid_type

Note: Freshsales does not support programmatic custom field creation via API, so Attribi could not auto-provision these. You need to create them manually:

  1. In Freshsales, go to Admin → Customize Module → Contacts → Custom Fields.
  2. Create both fields as Text (single-line). Attribi will auto-detect them when CRM-pull next runs (~5 min).
  3. In your form, populate hidden fields named exactly cf_mta_clickid and cf_mta_clickid_type.

If you don't want to create the fields, pixel-less mode still works without them — Attribi falls back to hashed-email + phone match.

LeadSquared

LeadSquared prefixes custom fields with mx_. Conventionally, MTA fields use:

  • mx_MTA_clickid
  • mx_MTA_clickid_type

Like Freshsales, LeadSquared does not support programmatic custom field creation via API. Manual setup:

  1. In LeadSquared, go to Settings → Customization → Lead Fields → Add Custom Field.
  2. Create both fields as Text (Schema name mx_MTA_clickid / mx_MTA_clickid_type).
  3. In your form, populate hidden fields named exactly mx_MTA_clickid and mx_MTA_clickid_type.

Verifying it works

After setup, send yourself a test lead:

  1. Click one of your own Google ads (or paste your landing URL with ?gclid=test_abc123).
  2. Submit the form on the landing page with a real email.
  3. In your CRM, find the new contact. Confirm mta_clickid = test_abc123 and mta_clickid_type = gclid.
  4. Wait up to ~5 minutes for Attribi's CRM-pull to run.
  5. In Attribi → Leads, find the lead. Click into the detail view. The touchpoints section should show one entry with click_id_platform: gclid.
  6. Mark the lead as closed-won (use the button on the lead detail page, or transition the deal stage in your CRM and wait for the next CRM-pull cycle).
  7. In your Google Ads UI, check Tools → Conversions → Recent uploads. You should see the conversion within a few minutes, attributed to the test_abc123 click.

If the touchpoint doesn't appear in Attribi: check that the custom field is populated in your CRM (most common issue is the field name mismatch — re-check the universal snippet's mta_clickid / mta_clickid_type names match your CRM's field names exactly, including the cf_ / mx_ / __c prefixes if applicable).


What if I don't do any of this?

Pixel-less mode works fine without click ID capture — your offline conversions still fire to ad platforms using hashed email + phone matching. Match rates drop from ~100% to ~30-60% depending on the platform, but real revenue is still attributed. Many customers ship with hashed-PII-only as a starting point and add click ID capture later.

If you eventually install Attribi's tracker.js, you don't need to remove this setup — the tracker takes precedence and captures full multi-touch attribution. The CRM custom fields stay populated as a fallback.


Privacy note

Click IDs are not personally identifiable information (PII) — they're random strings that ad platforms use to attribute clicks to their own logged-in users. Storing them in your CRM doesn't change your data-handling posture.

The hashed email + phone fallback that Attribi uses on ad platforms also preserves privacy — only one-way SHA-256 hashes leave your CRM, never raw values.


Questions or troubleshooting

  • "My form posts to a third-party endpoint, not the CRM directly" — populate the hidden fields anyway. As long as the CRM contact ends up with the values populated (via Zapier, Make, or your own integration), Attribi reads them when CRM-pull runs.
  • "My CRM has multiple form sources" — every form should populate the same two custom fields. Attribi tolerates last-write-wins semantics; the most recent click ID per contact is what gets attributed.
  • "What if a user clicks two different ads before submitting?" — the universal snippet uses localStorage and last-write-wins. The most recent click ID stored is what gets submitted. For full multi-touch attribution across ad platforms, install tracker.js.
  • "Where can I see if pixel-less mode is active?" — your Attribi connections page shows a yellow banner when the tracker is not detected. Once you install the tracker, the banner disappears within 7 days of the next pageview.

Last updated 2026-05-05.


Need help? Contact us or sign in to your connections page for account-specific tooltips.