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 click-level matching on ad-platform conversions using only your existing CRM and form code, for the CRMs that can hand the click ID back (HubSpot, Pipedrive, Zoho, Freshsales and LeadSquared; Salesforce is the exception, see below).


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 matching on ad-platform uploads. A click ID is matched deterministically by the platform that issued it; hashed email and phone are matched probabilistically against logged-in users, so a lower and platform-dependent share of those uploads is attributed. Neither share is something we can quote as a number.
  • ✅ The "Lead" stage event firing to ad platforms when leads first appear in your CRM (qualified and closed-won fire either way)

The setup takes about 15 minutes and lives entirely on your side. Attribi reads what you populate on its next scheduled CRM read, which runs once a day.


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 LeadSquared, Attribi creates them through LeadSquared's field API when you click Provision (since 2026-10-12); for Freshsales the fields have to be created manually, because its API does not 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, which is much cleaner.

Salesforce limit, read before you build this. The two fields are provisioned on the Lead object. When Salesforce converts the Lead, the Contact that the won Opportunity points at does not carry them, and Attribi's Salesforce read matches revenue on the contact's email and phone only. Capturing the click ID into Salesforce today keeps it for your own reporting; Attribi does not use it for matching yet. The Salesforce integration page lists this under "On the roadmap". Installing tracker.js is what gives Salesforce customers click-level matching now.

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. The hashed email and phone fallback still uploads every qualified and won outcome; the platform decides how many it can match, and that share is lower than with a click ID.

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 reads them on its next daily CRM run; nothing else to configure.
  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

Attribi creates the LeadSquared fields through its documented CreateLeadField API when you click Provision (since 2026-10-12). If your account answers that call with 404, the manual setup below applies:

  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

There are two different tests here. Keep them apart.

Test 1: does the field reach the CRM? This can use a made-up value.

  1. Open your landing URL with ?gclid=test_abc123 and submit the form with a test email address you will delete afterwards.
  2. In your CRM, find the new contact. Confirm mta_clickid = test_abc123 and mta_clickid_type = gclid.
  3. Delete or exclude that test contact before it reaches a qualified or won stage. A fabricated GCLID is not an ad click: Google will not attribute anything to it, and a test record that reaches closed-won would be uploaded as a real conversion with that invalid ID.

Test 2: does the click ID survive to the platform? This needs a real click from your own ad account and a real outcome.

  1. Click one of your own Google ads so the landing URL carries a real gclid, and submit the form with an address you can identify later.
  2. Confirm the contact in your CRM carries the real click ID in mta_clickid.
  3. Wait for Attribi's next daily CRM read.
  4. In Attribi, open Leads and find the lead. Its touchpoints section should show one entry with click_id_platform: google (the CRM stores the parameter name gclid; Attribi translates it to the platform name).
  5. Move that record to the stage you mapped as qualified, or mark it closed-won, and wait for the next daily read.
  6. In Google Ads, open Goals → Conversions → Uploads (older accounts: Tools → Conversions → Uploads) and look for the upload. Google's own guidelines say imported conversions take about 3 hours to appear in reporting. Remove the test outcome from your CRM afterwards so it does not stay in your revenue figures.

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 without click ID capture: your offline conversions still upload to ad platforms with hashed email and phone. Each platform then matches those hashes against its own logged-in users, and attributes a lower share than it would with a click ID; the share depends on the platform and on how many of your contacts gave the same email or phone to it. One Google-specific condition: Google accepts hashed identifiers only after you have accepted its Enhanced Conversions terms inside your Google Ads account. Until then Google rejects uploads that carry only hashed identifiers, and a pixel-less setup has no click ID to fall back on. Many customers ship with hashed identifiers 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

A click ID is a pseudonymous token the ad platform issued for one click. It identifies the click to that platform, not a person to you. Storing it in your CRM is a data-handling decision for your privacy owner, like any other field you keep about a contact.

The hashed email and phone fallback sends one-way SHA-256 hashes to the ad platforms, never raw values. Hashing does not replace consent: the platforms' own policies still require the disclosures and permissions that apply to your customers.


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 on its next daily CRM run.
  • "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-10-13.

Follow this guide in your own workspace.

Start a free Attribi workspace, connect your platforms, and set this up as you read.

No credit card. Free up to 500 visitors a month. New workspaces start with 30 days on Pro.

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