# SourceLoop Help Center, full markdown corpus > Marketing attribution software that ties every lead, call, chat, meeting, demo, and payment back to the campaign that produced it. SourceLoop captures the source and full journey behind every conversion, whether that conversion is a form submission, a phone call, a live chat, a booked meeting or demo, a subscription, or an ecommerce order. It then follows that person through your CRM to closed-won revenue, and syncs the result back to Google Ads, Meta, LinkedIn, TikTok, and Microsoft through their Conversions API. ## Who it is for Two shapes of business, both served by the same model: - Lead-led businesses, where a lead arrives as a form fill, phone call, live chat, appointment, quote request, or booking, and the question is simply which campaign produced it and which ones converted. Common in insurance, real estate, healthcare and clinics, home services and trades, events, legal, and local services. - Deal-led businesses, where a lead becomes an opportunity before it becomes revenue, moving through demo booked, meeting held, MQL, SQL, opportunity, and closed won. Common in B2B SaaS, agencies, and consultancies. Positioned between free analytics that stop at page views and enterprise attribution platforms that cost as much as a hire. Self-serve, no sales call required, first-party and cookieless by design. ## About this file This file is the complete contents of every published help article on SourceLoop, concatenated in section order. It exists so AI search engines and LLM browsers can ingest the full documentation in one fetch. For the per-article markdown variants, append `.md` to any `/help/{slug}/` URL. For the link index alone, see https://sourceloop.ai/llms.txt. Generated: 2026-09-11 --- # Section: Getting started Create your account, install the tracking pixel, invite your team, and capture your first attributed conversion. Everything you need on day one. # What is SourceLoop and how does it work? SourceLoop is marketing attribution software that captures the source and full journey of every lead, then pushes it to your CRM and ad platforms. Source: https://sourceloop.ai/help/what-is-sourceloop/ Updated: 2026-05-28 --- SourceLoop is marketing attribution software for lead-gen, SaaS, and ecommerce teams. It captures the **source and full journey** of every visitor that converts (form submission, meeting, chat, or payment) so you can see what marketing actually works. This article gives you a quick mental model of what SourceLoop does and where its data ends up. ## What SourceLoop tracks When you install the tracking pixel on your site, SourceLoop watches for four conversion types: - **Form submissions** from any form builder (HubSpot, Webflow, Typeform, custom) - **Meetings booked** through Cal.com, Calendly, HubSpot, Chili Piper, and others - **Chat conversations** started through Intercom, Drift, Crisp, and similar tools - **Payments** from Stripe (or any platform you connect) For every conversion, SourceLoop records: 1. The **original source** (first touch) — for example, `google / cpc` from a search ad 2. The **last source before converting** (last touch) 3. The **full journey** in between, including every page visit 4. UTM parameters, referrers, and device / location context ## Where the data lives > **It's not just another analytics dashboard** > SourceLoop is designed to push attribution data **into the tools you already > use**, not trap it in a separate dashboard. The dashboard is for analysis; > the integrations are where the value compounds. Captured data flows into three places: - **SourceLoop dashboard** at `app.sourceloop.ai` — reports, funnels, the Contacts Hub - **Your CRM** (HubSpot, Salesforce, Pipedrive, Zoho, etc.) — UTM fields and full journey appended to each lead - **Your ad platforms** (Google Ads, Meta, LinkedIn) — offline conversions synced via the Conversion API for closed-loop reporting ## Who it's for SourceLoop is built for teams who: - Run paid acquisition and need to know which campaigns drive **revenue**, not just clicks - Use a CRM and want **lead source + journey** stored on every record - Want to **sync offline conversions** (a closed deal, a paid invoice) back to ad platforms so the algorithms can optimize on real outcomes - Don't want to pay enterprise prices for Segment, HubSpot Enterprise, or attribution-only tools like Hockeystack If you don't have a CRM and don't run paid ads, you probably don't need SourceLoop yet. Stick with GA4. ## Next steps Once you've got the lay of the land, head to the [install guide](/help/install-the-tracking-pixel/) to get the pixel on your site. The whole setup takes about 5 minutes. ## Frequently Asked Questions ### What does SourceLoop actually do? It records the marketing source and full pre-conversion journey of every visitor that fills out a form, books a meeting, starts a chat, or pays you. Then it stitches that data into your CRM (HubSpot, Salesforce, Pipedrive), your dashboards (revenue by channel, multi-touch attribution, funnels), and your ad platforms (offline conversion uploads to Google Ads, Meta, LinkedIn, TikTok, Microsoft). ### How is SourceLoop different from Google Analytics 4? GA4 is session-level analytics (counts, sessions, events, conversions in aggregate). SourceLoop is contact-level attribution (one row per real person with their full journey, identity, lifecycle stage, and revenue attached). GA4 says "Paid Search drove 240 sessions"; SourceLoop says "Jane Doe at Acme Inc. converted on a $12k contract, first-touch was the Google Ads campaign on April 3, last-touch was the LinkedIn ad on May 19." Different lenses on the same data. ### How is SourceLoop different from HubSpot Marketing Hub? HubSpot's attribution lives behind Marketing Hub Pro and Enterprise tiers, and it's single-touch (one source per contact). SourceLoop adds multi-touch attribution (first, last, linear, U-shaped, time-decay), works on every HubSpot tier including Free, and pushes attribution into HubSpot rather than requiring you to live in it. ### Who is SourceLoop built for? B2B SaaS, B2B services, agencies, and ecommerce teams that run paid acquisition, use a CRM, and need to know which channels actually produce revenue (not just clicks). Solo founders with no paid spend and no CRM are better off starting with GA4 and graduating to SourceLoop once paid budgets matter. ### What does it cost? Free trial with no card required. Paid plans scale with monthly tracked visitors and number of integrations. See the pricing page for current rates. ### How long does it take to set up? The tracking pixel is a single script tag, takes about 30 seconds. After that, every additional integration (CRM, payment provider, ad platform, form tool) is its own short setup flow, typically under 10 minutes each. Most teams have a working attribution pipeline within an hour of signup. ### Does SourceLoop replace my CRM? No. SourceLoop is the attribution layer that feeds your CRM. Contacts created in SourceLoop sync into HubSpot, Salesforce, or Pipedrive with full source and journey data attached; your reps continue working in the CRM. Think of SourceLoop as the missing "where did this lead come from?" column on every CRM contact. ### Where do I start? Sign up for the free trial, install the tracking pixel on your site (5 minutes), and connect your most-used integration first (whichever payment provider, CRM, or ad platform matters most to you). The dashboard fills with real data within an hour. --- # How to sign up for SourceLoop Two ways to create your SourceLoop account in under a minute, Google or email + password. No credit card needed for the free trial. Source: https://sourceloop.ai/help/how-to-sign-up-for-sourceloop/ Updated: 2026-05-28 --- Signing up for SourceLoop takes about thirty seconds. There's a free trial that starts the moment you create the account, no credit card asked, no auto-charge, and the dashboard is live the instant you finish, no email confirmation step in the way. You have two options for creating the account. Pick whichever fits your team's auth habits. ## Before you start Nothing to prepare. Just have an inbox you can access (and your Google login, if you'd rather skip the password step). ## Step 1: Open the sign-up page Head to [app.sourceloop.ai/sign-up](https://app.sourceloop.ai/sign-up). ![SourceLoop sign-up page with the Google button at the top and email plus password form below](/help/screenshots/sourceloop-signup-page.webp) You'll see two options stacked on the page: - **Sign up with Google** (top button) - **Or sign up with email** (form below the divider) Pick one. ## Step 2: Create the account ### (1) Sign up with Google Click **Sign up with Google**. You'll be redirected to Google's consent screen, pick the Google account you want to use, approve the standard read-only profile scopes, and Google sends you straight back to SourceLoop. No password, no email confirmation, you land in the dashboard logged in. This is the fastest path if you (or your team) already use Google Workspace. ### (2) Sign up with email + password Below the divider, fill in: - **Email** — use the address you'd want password resets and product updates sent to. Most teams use a work email, but a personal email works fine for solo accounts. - **Password** — pick something strong. A password manager is the simplest way. Click the sign-up button. The account is created immediately and you're logged in, no confirmation email to click, the dashboard opens straight away. > **Use Google for team accounts** > If multiple teammates will eventually join the same workspace, using Google sign-up keeps every account tied to your company's Google Workspace, easier to manage, easier to offboard when someone leaves. ## What happens next Right after sign-up, you'll be in the SourceLoop dashboard at [app.sourceloop.ai/home](https://app.sourceloop.ai/home). A default workspace is already created for you, and the free trial is active. There's only one thing left to do before SourceLoop starts capturing leads: install the tracking pixel on your site. Head to [Install the SourceLoop tracking pixel](/help/install-the-tracking-pixel/) for the five-minute walkthrough. If you want to know what SourceLoop will start capturing once the pixel is in, [What is SourceLoop?](/help/what-is-sourceloop/) is a one-minute read. ## Frequently Asked Questions ### Do I need a credit card to sign up? No. The free trial starts the moment you create the account, no card required, no auto-charge at the end. You only add billing details if you decide to keep going on a paid plan. ### Which option should I pick, Google or email + password? Google is faster (one click, no email verification needed) and uses your existing Google identity, so there's no password to manage. Email + password works fine too if you'd rather keep auth providers separate. ### Do I have to confirm my email after signing up? No. The account is active immediately, you'll land in the dashboard right after sign-up. ### I signed up with Google but now want to use email + password (or vice versa). Can I switch? Email and Google sign-ins are tied to the same email address. If your Google account uses the same email you'd register with, you can sign in either way. If you want to change the email on your account, contact support, we can migrate it for you. ### I work with a team. Should everyone sign up separately or share one login? Each teammate signs up with their own account, then the workspace owner invites them. Shared logins lose every workspace permission, audit-log, and per-user role benefit. See [Invite teammates to your workspace](/help/) once your workspace is up. ### My sign-up failed with "User already exists". What now? Someone (likely past you, or a teammate) has already created an account with that email. Head to [app.sourceloop.ai/sign-in](https://app.sourceloop.ai/sign-in) and use **Forgot password** to recover it, or sign in with Google if you originally used Google. --- # How to book a SourceLoop demo Schedule a 30-minute walkthrough of SourceLoop against your real stack. See multi-touch attribution, CRM sync, and offline conversions in your funnel. Source: https://sourceloop.ai/help/book-a-demo/ Updated: 2026-05-28 --- Want to see what SourceLoop looks like running against attribution data that resembles yours? Grab thirty minutes on the calendar. We'll walk through your stack, plug in real examples, and figure out whether SourceLoop is the right fit. **[Book a demo](/demo/)** ## What happens on the call No slides, no script. We'll talk through what you're stuck on right now (leads landing in your CRM with "Direct" source, Google Ads spend you can't justify, no way to tie subscription revenue back to the campaign that drove signup) and show you how SourceLoop closes those loops on a workspace shaped like yours. You can ask anything, integrations, pricing, where the attribution model breaks, how renewals get credited, what the offline-conversion sync looks like for Google / Meta / LinkedIn, how it compares to Hockeystack or HubSpot Enterprise. We'd rather you leave knowing whether SourceLoop fits than walk away with a polished pitch. ## Who should book one The call makes the most sense if you're: - Spending real money on paid acquisition and can't tell which campaigns produce paying customers vs. just trial signups - Using a CRM (HubSpot, Salesforce, Pipedrive, Zoho) and want lead source + journey on every record without manual data hygiene - Running a SaaS or subscription business where lifetime revenue matters more than first-touch conversion counts - Evaluating SourceLoop against another attribution tool and need a side-by-side on a specific use case If you're a solo founder running zero paid spend, the [free trial](https://app.sourceloop.ai/sign-up) and the [tracking pixel install guide](/help/install-the-tracking-pixel/) get you further faster than a call would. ## Before the call Nothing to prepare. If you want to make it faster, have a couple of things in mind: - Your **rough monthly traffic** and conversion volume (forms, meetings, chats, payments) - The **ad platforms** you're spending on (Google Ads, Meta, LinkedIn, others) - Your **CRM**, if any, and how leads currently get into it - One **specific question** the call should answer, the more specific, the better the call goes > **Not ready to talk?** > [What is SourceLoop?](/help/what-is-sourceloop/) is a one-minute read on what the product covers, and [signing up](/help/how-to-sign-up-for-sourceloop/) is a thirty-second job that gets you into the dashboard with a free trial, no card asked. ## Frequently Asked Questions ### How long is the demo call? 30 minutes by default. We can extend to 45 if you have a complex stack or a longer list of questions; flag it in the booking notes and we'll block the extra time. Anything beyond that turns into a proper scoping session, which we run as a separate conversation. ### Who runs the demo? A SourceLoop product specialist with hands-on attribution experience, not a generic SDR. They have access to a sandbox workspace and can build live examples on the call against data shaped like yours. ### Will the call be a generic product pitch? No. We start by asking what you're stuck on right now (untrackable Direct traffic, unattributed Google Ads spend, no revenue-by-channel view, CRM hygiene), then show how SourceLoop solves the specific gap. If we can't solve it, we'll say so. ### Should I book a demo or just start the free trial? If you're a solo founder running little paid spend, the free trial plus the tracking-pixel install guide gets you further faster. If you're spending real money on ads, running a CRM, evaluating SourceLoop against another tool, or have an integration question that affects your architecture, the demo is worth the 30 minutes. ### Do I need to prepare anything? Nothing strictly required. Helpful to have in mind, monthly traffic and conversion volume, the ad platforms you spend on, the CRM you use, and one specific question the call should answer. The more specific the question, the more useful the call. ### Can I bring teammates? Yes. Marketing ops, RevOps, demand gen leads, and engineering folks who'd own the install all benefit from being on the same call. Booking accepts multiple attendees; just add them in the calendar invite. ### What happens after the demo? We send a follow-up with the workspace examples we built on the call, a short summary of next steps, and (if you asked) a pricing breakdown for your expected volume. No automated nurture sequence; if you don't reply, you don't hear from us. --- # Install the SourceLoop tracking pixel Add the SourceLoop tracking script to your site in three steps. Every visitor's source, journey, and conversion then gets captured automatically. Source: https://sourceloop.ai/help/install-the-tracking-pixel/ Updated: 2026-05-28 --- This guide walks you through adding the SourceLoop tracking pixel to your **marketing site**. Once installed, SourceLoop captures every visitor's source, journey, and any conversion (form submission, meeting, chat, payment) they trigger. Three steps, around five minutes. Works on every CMS, every framework, every static-site generator. ## Which install do you need? > **Start here** > **Marketing site only** (no logged-in product): this page is all you need. Continue below. > > **You also have an app** at something like `app.yoursite.com` where users sign up and log in: do this page for the marketing site first, then do [Install SourceLoop in your app](/help/install-the-sourceloop-sdk/) for the app. The app needs different settings so your **login** forms are not counted as conversions and in-app clicking does not burn your pageview allowance. That page also covers the npm SDK, if you would rather write code than paste a snippet. ## Before you start You'll need: - A **SourceLoop account** ([sign up here](/help/how-to-sign-up-for-sourceloop/) if you don't have one yet) - **Access to your site's `` HTML** (or a tag manager like GTM) - A **published page** to test against once it's live ## Step 1: Copy the tracking snippet 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. In the left sidebar, open **Setup -> Tracking code**. 3. Copy the snippet shown on the page. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) The snippet looks like this: ```html ``` The `websiteId` is unique to your workspace and is filled in automatically when you copy from the app. Don't share the snippet publicly, anyone who has it could send fake events to your account. > **Running multiple sites under one workspace?** > Switch the active website in the top-left workspace switcher before you copy. The snippet is scoped to whichever website is currently selected, so each **separate** brand or product gets its own `websiteId`. > > A marketing site and its own app are **not** separate sites. `www.yoursite.com` and `app.yoursite.com` share one `websiteId`, which is what keeps a visitor who reads your homepage and then signs up recorded as one person. ## Step 2: Paste it into your site's `` Add the snippet inside `` on every page. The exact location depends on your stack: - **Webflow**: Project Settings -> Custom Code -> Head Code - **WordPress**: Use the "Insert Headers and Footers" plugin (or "WPCode"), or paste into your active theme's `header.php` just before `` - **Next.js / Nuxt / Remix**: Paste into the root layout file's `` (e.g., `app/layout.tsx`, `nuxt.config.ts`) - **Astro**: Add to the global layout's `` (e.g., `src/layouts/BaseLayout.astro`) - **Shopify**: Online Store -> Themes -> Actions -> Edit Code -> `theme.liquid`, paste before `` - **BigCommerce**: Storefront -> Script Manager -> Create a script, set placement to `` on All Pages - **Wix**: Settings -> Custom Code -> Add Custom Code, placement ``, apply site-wide - **Squarespace**: Settings -> Advanced -> Code Injection -> Header - **Framer**: Site Settings -> General -> Custom Code -> Start of `` tag - **Google Tag Manager**: New Tag -> Custom HTML -> paste the snippet, trigger on All Pages - **Plain HTML / custom build**: Paste directly into your site template's `` > **Consent management platforms can block the script** > If you use OneTrust, Cookiebot, Iubenda, or any other consent banner that blocks scripts until consent is given, make sure SourceLoop is allowlisted in the analytics category. Otherwise the snippet only fires for users who accept cookies, and you'll miss attribution for everyone else. ## Step 3: Deploy and reload Push the change live, then open your site in an **incognito window** (regular browsers may cache the old version of the page without the new snippet). That's it. Visitors hitting your site from this point on are being tracked by SourceLoop, source, journey, and any conversions they trigger. ## What gets captured automatically On a marketing site the snippet captures **every form** it can see, which is what you want, since almost every form on a marketing site is a lead form. Sign-in, password reset, and one-time-code forms are recognised as credential forms and skipped, so a customer login area on your marketing site does not create fake leads. If you need to exclude a specific form (an internal tool, a search box, a spam trap), or if you are installing on a logged-in product where most forms are **not** leads, see [Install SourceLoop in your app](/help/install-the-sourceloop-sdk/) for the form-exclusion and conversions-mode options. ## Verify it's working Want to make sure the script is firing correctly? Head to [Verify the tracking pixel is installed](/help/verify-tracking-is-working/) for the two-minute check. If you're stuck, email us at hello@sourceloop.ai with your domain and we'll take a look. ## Frequently Asked Questions ### Where exactly does the script go? Inside the `` tag of every page you want to track. Put it before any other analytics scripts so SourceLoop loads first. ### Does the script slow down my site? The tracker is under 8KB gzipped and loads async, so it has no measurable impact on Core Web Vitals. SourceLoop also pre-connects to `app.sourceloop.ai`, which shaves another ~80ms off the handshake. ### What if I'm using Google Tag Manager? Create a Custom HTML tag, paste the snippet, and set the trigger to "All Pages". Make sure the tag fires before any consent management platform that might block scripts. ### Do I need to install the script on every page? On every page of your marketing site, yes. Site-wide install in `` is the simplest path and every CMS and framework supports it, because forms, meetings, chats, and payments only get attributed on pages where the snippet is loaded. Your logged-in app is the exception, it takes a different install, see Install SourceLoop inside your app. ### My site uses a strict Content Security Policy. Will SourceLoop be blocked? If your CSP doesn't already allow `app.sourceloop.ai`, the browser will block the script. Add `app.sourceloop.ai` to your `script-src` and `connect-src` directives. The browser console will show a CSP violation if this is the cause. ### I have multiple websites under one SourceLoop workspace. Do I install the same snippet on all of them? It depends on whether they are separate properties or one property. Separate brands or products each get their own `websiteId`, so switch the active website in the dashboard before copying the snippet. But a marketing site and its own app (`www.yoursite.com` and `app.yoursite.com`) are one property and must share a single `websiteId`, otherwise the visit and the signup are recorded as two different people. --- # Verify the SourceLoop tracking pixel is installed Two minutes to confirm the tracking pixel is firing on your site. Use the built-in Verify button in SourceLoop, or check DevTools manually. Source: https://sourceloop.ai/help/verify-tracking-is-working/ Updated: 2026-05-28 --- You've added the SourceLoop snippet to your site (if not, do that first via [Install the SourceLoop tracking pixel](/help/install-the-tracking-pixel/)). This guide confirms the script is actually firing. There are two ways to check: the built-in **Verify** button in SourceLoop (fastest, recommended), or manual inspection in your browser's DevTools (useful for debugging edge cases). ## Step 1: Open the Tracking code page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. In the left sidebar, open **Setup -> Tracking code**. 3. Scroll to the **Installation status** section near the top of the page. If SourceLoop has already detected the script on its own (it polls periodically), you'll see a green **Verified** badge with a timestamp. If not, the badge reads **Not detected**, click the **Verify** button to check manually. ## Step 2: Click Verify 1. Click the **Verify** button on the Tracking code page. 2. SourceLoop fetches your site's URL, looks for the tracking snippet, and reports back within a few seconds. 3. You'll see one of two outcomes: - **Success** — a green toast appears, the badge flips to **Verified**, and the last-verified timestamp updates. You're done. - **Failed** — a red toast appears with a short reason (e.g., "Script not detected", "No active subscription", "Website not found"). See the troubleshooting section below. > **Recently deployed?** > If you've just pushed the snippet to your site, give it 30 seconds to propagate through your CDN before clicking Verify. If you're behind Cloudflare or similar, purge the cache for the page you're verifying. ## Step 3: Cross-check in DevTools (optional) For an independent check, or if Verify is failing and you want to debug: 1. Open your site in an **incognito window**. 2. Open **DevTools** (Cmd+Opt+I on Mac, Ctrl+Shift+I on Windows) and switch to the **Network** tab. 3. Reload the page. 4. Filter the network requests for `sourceloop`. 5. You should see at least two requests: - **`tracking-v3.js`** — returns **200 OK**. This is the SourceLoop tracker bundle. - **A POST to `app.sourceloop.ai`** — returns **200** or **204**. This is the page-view event the tracker sends on load. If both requests are present and successful, the pixel is firing correctly. If `tracking-v3.js` returns 4xx or 5xx, the script URL is being blocked or your `websiteId` is wrong, double-check the snippet you pasted matches what's shown in SourceLoop. ## What to do if Verify keeps failing Run through these in order until one fixes it: 1. **Confirm the page is publicly reachable.** Verify can only check pages that don't require login. If your site is behind a paywall, password, or staging URL, switch to the DevTools method above on a logged-in browser session. 2. **Check your ``.** View page source (Cmd+Opt+U on Mac, Ctrl+U on Windows) and search for `app.sourceloop.ai`. If you don't see the snippet there, it's been overridden, deferred too late, or pasted in the wrong template. 3. **Check for CSP violations.** Open DevTools -> Console. If you see "Refused to load the script `https://app.sourceloop.ai/tracking-v3.js` because it violates the following Content Security Policy directive", add `app.sourceloop.ai` to your `script-src` and `connect-src` directives. 4. **Check your consent banner.** Cookiebot, OneTrust, Iubenda, and similar tools block scripts until consent is accepted. Either allowlist SourceLoop in the analytics category, or accept consent on the test page before clicking Verify. 5. **Check the CDN cache.** If you use Cloudflare, Vercel, Netlify, or a self-hosted CDN, the old HTML (without the snippet) may still be cached. Purge the cache for the page you're verifying. 6. **Check the `websiteId`.** Confirm the `websiteId` in the snippet on your site matches the one shown in SourceLoop's Tracking code page for the currently selected website. Multi-site workspaces are the most common place this goes wrong. If you've tried all six and Verify still fails, email us at hello@sourceloop.ai with your domain and the exact error message from the failed toast, we'll take a look. ## What to do once it's verified The tracker is now capturing visitor sessions, sources, UTMs, and journeys on every page. The next step is connecting the conversion sources you actually want attributed: - **Forms** → [Web form tracking](/help/category/attribution-tracking/) covers every supported form tool - **Meetings** → [Meeting tracking](/help/category/attribution-tracking/) covers Calendly, Cal.com, HubSpot, and others - **Chats** → [Chat tracking](/help/category/attribution-tracking/) covers Intercom, Crisp, Tidio, and others - **Payments** → [Payment tracking](/help/category/attribution-tracking/) covers Stripe, Lemon Squeezy, Paddle, Polar, Dodo Each integration article walks through the setup specific to that tool. ## Frequently Asked Questions ### How long after I install the snippet can I verify? Right away. Once the snippet is live on a published page, the Verify button can confirm it within a few seconds. The only delay is your CDN / cache, if your site is behind Cloudflare or similar, purge the cache for the page you're verifying. ### Verify said success but I'm not seeing leads in the Contacts Hub. What's wrong? Verify only confirms the tracking script loads. Leads only appear when a conversion event fires (form submission, meeting booking, chat opened, payment). If the script is loading but no conversions are coming through, the issue is either that no conversions have happened yet, or your conversion source isn't wired up. Check the relevant integration article (web form, meeting, chat, payment) for that tool. ### Verify keeps failing even though I'm sure the script is on the page. Why? Most common causes, in order, are (1) the page you're verifying is behind a login wall or paywall (the verifier can't see private pages), (2) a CSP or bot-blocker is blocking SourceLoop's verification request, (3) the snippet is loaded conditionally (for example only after consent is given), or (4) the page is cached at the CDN with an older HTML that didn't include the snippet. ### Can I verify on a subdomain or a staging site? Yes. The Verify button checks whatever URL the website record points to. If you want to verify a different page than the homepage, update the website URL in Settings, or use the DevTools method described below. ### Does Verify check every page, or just the homepage? It checks the URL stored on the website record (usually your homepage). For coverage across other pages, browse the site in DevTools with the network tab open, or use the `?sl_debug=1` URL param. --- # GDPR, cookies, and consent with SourceLoop SourceLoop does not put you at legal risk, and compliance is simple. Most sites do nothing or flip one toggle. Three setups, about a minute. Source: https://sourceloop.ai/help/gdpr-cookies-and-consent/ Updated: 2026-06-26 --- ## The 30-second decision | Your situation | What to do | | --- | --- | | Your visitors are in the US (or outside the EU/UK) | **Nothing.** Keep a privacy policy and you're done. | | You have EU/UK visitors and want the simplest setup | Turn on **Cookieless mode** (one toggle, usually no banner needed). | | You have EU/UK visitors and want maximum accuracy | Keep the default install and gate it with a **consent tool**. | Whichever you pick, SourceLoop already honors browser privacy signals (Global Privacy Control, Do Not Track) and Google Consent Mode automatically. You do not have to configure those. ## How SourceLoop tracks, in plain terms - It is a **first-party** script on your own site. No third-party pixels. - By default it uses **first-party cookies** to recognize a returning visitor, which gives the best accuracy. - Your visitor's IP is processed but stored only as a **hashed** value, never in the raw. - When someone submits a form, it captures the lead details (such as email) to attribute the conversion. All of this data goes only to your own SourceLoop account. ## Option A: Do nothing (US and non-EU audiences) GDPR is an EU and UK law. If your business and visitors are in the US, you fall under US rules (such as CCPA), which are **opt-out, not opt-in**. That means you do **not** need an "accept first" cookie banner. What you should still do: - Keep a **privacy policy** that mentions you use analytics. - That's it. SourceLoop already respects the browser opt-out signal (Global Privacy Control), so anyone who opts out is excluded automatically. This covers the majority of sites. ## Option B: Cookieless mode (privacy-first, usually no banner) If you have EU or UK visitors and want the easiest compliant setup, turn on **Cookieless mode**. How to enable it: 1. Open your website's **Tracking code** page in SourceLoop. 2. Toggle on **Cookieless mode (GDPR)**. 3. Copy the updated snippet and replace the one on your site. ![SourceLoop Setup Tracking Code page with the Cookieless mode (GDPR) toggle switched on. The tracking snippet above updates to include cookieless: true, and the toggle row shows a PRIVACY-FIRST label with the note that no cookies or local storage are set so you usually do not need a consent banner.](/help/screenshots/cookiless-sourceloop.webp) What it does: - Sets **no cookies and no local storage**. Nothing persistent is stored on your visitor's device. - Visitors are recognized by a privacy-friendly **server signal that resets every day**, so there is no long-term identifier following anyone around. - Because nothing is stored on the device, you **usually do not need a cookie-consent banner**. This is the same approach used by privacy-first analytics tools like Plausible and Fathom. The tradeoff: - Slightly **lower accuracy for returning visitors and long multi-touch journeys**. Because the signal resets each day, the same person can look like a new visitor on a different day. Best for: EU-heavy sites that want the simplest compliant setup and are comfortable with a small loss in long-window accuracy. ## Option C: Consent management tool (full accuracy, fully compliant) If you want the most accurate attribution from the people who do consent, keep the default install and let a consent banner control it. One thing to know first: a **basic cookie banner is just a popup**. By itself it does not actually block anything. To genuinely gate tracking you need a consent tool that can hold scripts until the visitor accepts. Common options, most with free tiers: **Cookiebot, CookieYes, Termly, Iubenda, OneTrust, Osano**. There are two easy ways to wire it up. **1. Tag the snippet in your consent tool.** Mark the SourceLoop script as analytics or marketing, and the tool holds it until the visitor accepts. For a Cookiebot-style tool, the script tag looks like this: ```html ``` The `type="text/plain"` plus the data attribute is what keeps it switched off until the visitor accepts. The exact attribute name differs per tool, and each one documents it. **2. Use Google Consent Mode v2.** If your banner supports Consent Mode, just set analytics to denied by default. SourceLoop reads it automatically and stays completely off (no cookies, no events) until the visitor accepts, then turns on. The tradeoff: - Visitors who **reject** are not tracked, including their conversions. That is the correct and compliant outcome, and it is the same for every analytics tool. You keep full, accurate data for everyone who accepts. Best for: EU sites that want maximum accuracy from consenting visitors. ## What SourceLoop already does for you No setup required for any of this: - **First-party only.** No third-party cookies. - **IP stored hashed**, never raw. - **Honors Global Privacy Control and Do Not Track.** If a visitor has these on, tracking is disabled or personal data is stripped automatically. - **Reads Google Consent Mode v2** from your banner if present. Analytics denied means no tracking; ad-user-data denied means no email or phone is sent; ad-storage denied means no ad click IDs. ## Which should I choose? - **US or non-EU audience:** Option A. Do nothing. - **EU audience, want it simple:** Option B. Cookieless mode. - **EU audience, want best accuracy:** Option C. Consent tool. You can switch any time by changing your snippet, so it is easy to start simple and revisit later. > **Not legal advice** > This guide is general information, not legal advice. Privacy rules vary by country and by how you use the data. If you are unsure, check with a privacy professional for your specific situation. ## Frequently Asked Questions ### Will I get in trouble just for using SourceLoop? No. Using an analytics tool is not illegal. The requirement is about getting consent for EU and UK visitors. Pick Option B (Cookieless mode) or Option C (a consent tool) for those visitors and you are set. ### Does cookieless mean I capture nothing? No. You still get page views, traffic sources, and conversions. You only lose some precision when connecting the same person across different days, because the privacy-friendly server signal resets every day. ### If someone rejects the consent banner, do I still see their conversion? In Option C, no, by design. A rejection means that person is not tracked, including their conversions. This is the same for every analytics tool, and it is what compliance requires. You keep full, accurate data for everyone who accepts. ### Can I change my mind later? Yes. Switching between the default install, cookieless mode, and consent-gated tracking is just a change to the snippet on your site, so it is easy to start simple and revisit later. ### Do I have to configure Global Privacy Control or Google Consent Mode myself? No. SourceLoop honors Global Privacy Control, Do Not Track, and Google Consent Mode v2 automatically, on every setup, with no configuration. If a visitor opts out or your banner sets analytics to denied, tracking is disabled or personal data is stripped automatically. --- # Install SourceLoop in your app (SaaS, logged-in product) Install SourceLoop on your app subdomain with the script tag or npm SDK, so signups are captured but logins and in-app forms never count as conversions. Source: https://sourceloop.ai/help/install-the-sourceloop-sdk/ Updated: 2026-08-21 --- If you run a SaaS product you have **two different surfaces**, and they need **two different installs**: | Surface | Example | Install | What gets captured | |---|---|---|---| | **Marketing site** | `www.yoursite.com` | Script tag, **full mode** | Pageviews, attribution, every lead form, automatically | | **Your app** | `app.yoursite.com` | Script tag or npm SDK, **conversions mode** | Only the signups and payments you record explicitly | This page covers the **app**. For the marketing site, see [Install the tracking pixel](/help/install-the-tracking-pixel/) instead, and if you have no logged-in product at all, that page is the only one you need. > **Do not install the marketing snippet as-is inside your app** > The default snippet is built for a marketing site, so it captures **every** form it can see. Dropped into a logged-in app it will also watch your invite forms, settings forms, support forms, and search boxes, and every in-app page view counts against your pageview allowance. Conversions mode, in Step 2, is the fix. ## Why the app is different On a marketing site almost every form is a lead form, so capturing all of them is exactly right. Inside an app the opposite is true: most forms are product UI, and only one moment, the signup, is a conversion. There are three specific problems, and conversions mode solves all three at once: 1. **Login forms look like lead forms.** A sign-in submit and a trial signup are nearly identical in structure. The password is never collected either way, so what reaches SourceLoop is an email address, which is precisely what a lead looks like. 2. **Every internal form is a candidate.** "Invite a teammate", "change billing email", and "contact support" are all forms with an email field in them. 3. **In-app navigation is not marketing signal.** A user clicking through twenty screens of your product tells you nothing about attribution, but it does consume your pageview allowance. ## Step 1: Choose script tag or npm SDK Both send data to the same place, so pick whichever fits your stack. You can mix them: script tag on the marketing site, SDK in the app. | Method | Best for | Needs code? | |---|---|---| | **Script tag** | Any app, including ones you cannot easily add a dependency to | No, paste one snippet | | **npm SDK** (`@sourceloop-analytics/sdk`) | Apps built with React, Next.js, Vue, or Node, especially when you need the server client | Yes, a few lines | > **Use the same websiteId everywhere** > One SourceLoop website covers your marketing site **and** your app. `www.yoursite.com` and `app.yoursite.com` share the identity cookie on your root domain automatically, so a visitor who reads your homepage and then signs up stays **one person**. Using a second `websiteId` for the app is the most common way to break attribution. Find yours in the dashboard under **Settings, Tracking Code**. ## Step 2: Install in conversions mode Add SourceLoop to your **app's** root layout using the same `websiteId` as your marketing site, plus `mode: 'conversions'`. ### Script tag ```html ``` ### npm SDK ```bash npm install @sourceloop-analytics/sdk ``` Call `init()` as early as possible, once per page load. In Next.js App Router, put it in a client component rendered from the root layout. ```tsx // app/sourceloop-init.tsx 'use client'; import { useEffect } from 'react'; import { init } from '@sourceloop-analytics/sdk'; export function SourceLoopInit() { useEffect(() => { init({ websiteId: 'YOUR_WEBSITE_ID', mode: 'conversions' }); }, []); return null; } ``` ```tsx // app/layout.tsx import { SourceLoopInit } from './sourceloop-init'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### What the two modes actually do ```ts init({ websiteId: 'YOUR_WEBSITE_ID' }); // 'full' — marketing site init({ websiteId: 'YOUR_WEBSITE_ID', mode: 'conversions' }); // inside your logged-in app ``` | | `full` (marketing site) | `conversions` (your app) | |---|---|---| | Pageviews and SPA navigations | Captured | **Off** | | Automatic form capture | Every non-credential form | **Off** | | Scroll, click, and engagement events | Captured | **Off** | | Page speed metrics | Captured | **Off** | | Chat and embedded-widget capture | Captured | **Off** | | Visitor identity and attribution cookie | Kept | **Kept** | | `identify()`, `track()` | Available | **Available** | The last two rows are the important ones. Conversions mode is **not** "SourceLoop off". The visitor's identity and original marketing source are still carried into the app, so when you record a signup it still attributes back to the ad or search that started it. You are only switching off the automatic guessing. ## Step 3: Record the signup yourself Because nothing is captured automatically now, the signup is something you call explicitly. This is a feature: it fires on account creation and nowhere else, so a login can never be mistaken for one. ```ts import { identify, track } from '@sourceloop-analytics/sdk'; // After the account is actually created — not on the login screen identify({ email: user.email }); track('signup_completed', { plan: 'free_trial' }); ``` > **identify on every login is correct** > Call `identify()` on **login as well as signup**. It only says "this visitor is this person" and never creates a conversion, so it is safe to run every time. `track('signup_completed')` is the one that must fire only once, at account creation. For **Google, GitHub, or any other social sign-in**, the email only exists on your server, so this has to happen in your auth callback rather than in the browser. That case, along with Stripe and other subscription stitching, is covered step by step in [Track SaaS signups, trials, and subscriptions](/help/track-saas-signups-and-subscriptions/). ## Check that logins are not showing up Sign in to your own app with a test account, then open **Contacts** in the SourceLoop dashboard. - A **signup** should appear as a new conversion, attributed to the source that originally brought that visitor in. - A **login** should produce no new conversion at all. The existing contact may update, which is expected and correct. If a login is creating conversions, jump to [Troubleshooting](#troubleshooting). ## If you want automatic capture in your app anyway Some teams do want the app on full mode, usually because the signup form lives on the app subdomain and they would rather not write any code. That is supported, and there are two layers of protection. ### Credential forms are already skipped Even in full mode, SourceLoop recognises credential surfaces and does not capture them: - Sign-in and log-in forms, in any of the common spellings - Password reset, forgotten password, and "set a new password" forms - One-time codes, 2FA, authenticator, and magic-link forms - Pages living at credential routes such as `/login`, `/forgot-password`, or `/auth/...` The rule deliberately leans one way: **when a form is genuinely ambiguous, it is captured.** Missing a real signup costs you attribution you can never recover, while an occasional stray login is easy to spot and clean up. You can make the decision unambiguous from your side, and it is worth doing: - Put `autocomplete="new-password"` on the password field of your **signup** form. This is the strongest possible "this is account creation" signal and it overrides everything else, including a `/login` URL. It is also what password managers use to offer a generated password, so it is good practice regardless. - Put `autocomplete="current-password"` on the password field of your **login** form. - Give the forms honest labels. A submit button reading "Sign in" or a form with `id="login-form"` is recognised; a button reading "Continue" on a page at `/account` is not. > **Sign-in and sign-up on the same route** > If one route toggles between sign-in and sign-up, the `autocomplete` attributes are what tell the two apart, since the URL cannot. Set them and both cases behave correctly. ### Exclude your own forms explicitly For everything that is neither a credential form nor a lead, list it in the snippet. This is the tool for invite forms, settings forms, internal search, and support widgets. ```html ``` | Option | Matches against | Matching | |---|---|---| | `form_ids` | The form's `id` | Exact | | `form_classes` | One of the form's classes | Exact | | `form_patterns` | The form's `id` or `class` | Regex | | `field_patterns` | The `name` of any field in the form | Regex | | `excludedPagePatterns` | The current URL | Regex, skips the page entirely | `excludedPagePatterns` is the blunt one: a matching page sends **nothing at all**, not even a pageview. Use it for whole areas of your product that have no marketing meaning. > **Which approach should I pick?** > If your app has more than a handful of forms, **conversions mode is the better answer.** Exclusion lists are a permanent maintenance task, every new form your team ships is a form someone has to remember to exclude. Conversions mode inverts the default, so new product UI is silent unless you opt it in. ## SDK reference One package gives you both a browser client and a server client. - **Browser code:** `import { ... } from '@sourceloop-analytics/sdk'` - **Server code:** `import { ... } from '@sourceloop-analytics/sdk/server'` The server client requires **Node 18+** (it uses the built-in `fetch`). ### Browser ```ts import { identify, track, reset, checkoutMetadata } from '@sourceloop-analytics/sdk'; identify({ email: user.email }); // on login/signup (idempotent) track('signup_completed', { plan: 'pro' }); // a conversion you want recorded reset(); // on logout const meta = checkoutMetadata(); // { sourceloop_anonymous_id } for client checkouts ``` ### Server, for OAuth signups and payments The server client reads the visitor's id from the request cookie and binds it. This is the reliable way to attribute things that happen on your backend (Google/GitHub login, Stripe checkout). ```ts import { Sourceloop, getAnonymousId, checkoutMetadata } from '@sourceloop-analytics/sdk/server'; const sl = new Sourceloop({ websiteId: 'YOUR_WEBSITE_ID' }); const anonymousId = getAnonymousId(req); // reads the _sl_aid cookie off the request await sl.identify({ anonymousId, email: user.email }); await sl.track({ anonymousId, email: user.email, eventName: 'signup_completed' }); ``` Full examples for NextAuth, Clerk, Supabase Auth, and Stripe are in [Track SaaS signups, trials, and subscriptions](/help/track-saas-signups-and-subscriptions/). ### Your website id is not a secret Paste your `websiteId` straight into the code, both in the browser and on the server. It is already visible in your site's tracking snippet, so there is nothing to hide and **no environment variable to set up**. (If you prefer env vars for managing multiple environments you still can, but it is optional.) ### Which install goes where | Where | Install | What it captures | |---|---|---| | Marketing site (WordPress/Webflow/...) | Script tag, full mode | pageviews, forms, attribution | | App frontend (React/Next/Vue) | Script tag or `@sourceloop-analytics/sdk`, `mode: 'conversions'` | `identify` on login, explicit signup conversions, client checkout metadata | | App backend (Node/Next API routes) | `@sourceloop-analytics/sdk/server` | OAuth signups, server-side checkout stitching, server conversions | You do **not** need a separate SourceLoop account or website id per surface. What changes between surfaces is the **mode**, not the id. ## Verify it is working 1. Open your app, then open the browser DevTools, **Application, Cookies**. You should see a `_sl_aid` cookie set on your root domain (e.g. `.yoursite.com`). 2. Trigger a test signup and confirm the conversion appears in your dashboard. 3. Trigger a test login and confirm **no** new conversion appears. ## Troubleshooting **Logins are creating conversions.** Switch that surface to `mode: 'conversions'`. If you need to stay on full mode, add `autocomplete="current-password"` to the login form's password field and give the form a recognisable id such as `login-form`. **Signups stopped being captured after I labelled my login form.** Your signup form is probably being caught by a shared attribute or a shared route. Add `autocomplete="new-password"` to the signup password field, which overrides every other signal, and confirm the submit button does not read "Sign in". **In-app forms like "invite a teammate" are showing as leads.** Either move to conversions mode, or add the form to `formExclusion`. Matching a field name via `field_patterns` is usually the most durable, since ids and classes change when components are restyled. **My pageview count is much higher than my traffic.** Full mode is installed inside your app and in-app navigation is being counted. Conversions mode fixes this. **No conversions at all after switching to conversions mode.** Expected until you add the calls. Conversions mode captures nothing on its own, so `identify()` and `track()` from Step 3 have to be wired up. **No `_sl_aid` cookie.** The snippet is not on the page, or `websiteId` is wrong. View source and confirm the snippet is present with the correct id. **`@sourceloop-analytics/sdk` import errors on the server.** Make sure you are on Node 18+ and importing server helpers from `@sourceloop-analytics/sdk/server`, not the package root. **Marketing visits and app signups appear as two different people.** Almost always a different `websiteId` between the two surfaces. If your app is on a separate root domain instead of a subdomain, see [Cross-domain and subdomain tracking](/help/cross-domain-and-subdomain-tracking/). ## Checklist 1. Marketing site on the standard snippet, **full mode**. 2. App on the same `websiteId`, **`mode: 'conversions'`**. 3. `identify()` on login and signup, `track('signup_completed')` at account creation only. 4. Social sign-ins and subscription revenue wired up per [Track SaaS signups, trials, and subscriptions](/help/track-saas-signups-and-subscriptions/). 5. Test login produces no conversion, test signup does. ## Frequently Asked Questions ### Do I need this page, or is the tracking pixel enough? If you only have a marketing site, the tracking pixel page is enough and you can ignore this one. You need this page if you also have a logged-in product where people sign up and log in, because the default install would treat your login and in-app forms as conversions and count in-app navigation against your pageview allowance. ### Will SourceLoop count my login form as a conversion? No. The tracker recognises credential surfaces (sign-in, password reset, OTP, magic link) and skips them on every install. It errs toward capturing when a form is genuinely ambiguous, because missing a real signup is worse than letting one login through. If a login form on your app still slips through, conversions mode turns off automatic form capture entirely. ### What is the difference between full mode and conversions mode? Full mode is the marketing-site install. It captures pageviews, engagement, and every non-credential form automatically. Conversions mode is the app install. It captures nothing automatically, only the identify and track calls you write yourself, so in-app navigation and internal forms never become pageviews or conversions. ### Do I need a separate SourceLoop account or website id for my app? No. One website id covers your marketing site and your app, including subdomains like www.yoursite.com and app.yoursite.com. Use the same websiteId in the script tag and in the SDK so the journey stays as one visitor. What changes between the two surfaces is the mode, not the id. You find your websiteId in the dashboard under Settings, Tracking Code. ### Is my website id a secret? No. Paste it straight into your code, in the browser and on the server. It is already visible in your site's tracking snippet, so there is nothing to hide and no environment variable to set up. If you prefer env vars to manage multiple environments you still can, but it is optional. ### Can I use the script tag and the npm SDK together? Yes. They send data to the same place, so a common setup is the script tag on your marketing site and the SDK in your app. Just use the same websiteId everywhere. ### Will in-app browsing use up my pageview allowance? Not in conversions mode. Pageviews, SPA navigations, engagement events, and web vitals are all switched off, so a user clicking around inside your product sends nothing to SourceLoop until you explicitly record a conversion. ### What Node version does the server client need? Node 18 or newer. The server client uses the built-in fetch, which is available from Node 18. Import server helpers from "@sourceloop-analytics/sdk/server", not the package root. --- # Track SaaS signups, trials, and subscriptions Attribute the full SaaS journey, from visit to signup, free trial, and paid subscription, back to the original click. Next.js and Stripe examples. Source: https://sourceloop.ai/help/track-saas-signups-and-subscriptions/ Updated: 2026-06-29 --- This guide attributes the **entire** SaaS journey to the marketing source that started it: ``` Ad / SEO / referral → visits www.yoursite.com (marketing) → clicks "Start free trial" → app.yoursite.com (your app) → signs up (email form, OR Google / Facebook OAuth) → uses the free trial (no payment yet) → later subscribes (Stripe / LemonSqueezy / Paddle / Polar) ``` By the end, the signup **and** the eventual subscription revenue are both credited to the original ad/click. ## Prerequisites 1. **Tracking installed on both the marketing site and the app**, using the **same `websiteId`**: the standard snippet on the marketing site ([Install the tracking pixel](/help/install-the-tracking-pixel/)) and `mode: 'conversions'` in the app ([Install SourceLoop in your app](/help/install-the-sourceloop-sdk/)). Subdomains (`www.` and `app.`) share identity automatically, no extra setup. 2. **Your payment provider connected in SourceLoop**, under **Setup, Payment**. This is what lets SourceLoop receive the subscription and payment events. You only do it once, and it is [Step 3](#step-3-connect-your-payment-provider) below. 3. The **server SDK** installed in your app's backend (`npm install @sourceloop-analytics/sdk`, Node 18+). Strongly recommended, it is what makes OAuth signups and payments attribute reliably. ## Key concept: identify vs track You will use exactly two calls. Understanding the difference avoids 90% of mistakes. - **`identify({ email })`** links a known person (their email) to their anonymous visit. It is **idempotent** and creates **no** conversion. Call it **every time** a user logs in or signs up. Safe to call repeatedly. - **`track({ eventName })`** records a **conversion** (a meaningful event like `signup_completed` or `trial_started`). Call it **once** per real event, not on every page load. Rule of thumb: **`identify` = "this visitor is this person"**, **`track` = "this important thing happened"**. ## Cross-subdomain note (important) When the user clicks from `www.yoursite.com` to `app.yoursite.com`, SourceLoop already treats them as the **same visitor** because the identity cookie (`_sl_aid`) is set on your root domain (`.yoursite.com`). **You do not need to do anything** for subdomains. (If your app is on a genuinely *different root domain*, e.g. marketing on `brand.com`, app on `brandapp.io`, see [Cross-domain tracking](/help/cross-domain-and-subdomain-tracking/).) ## Step 1: Capture the signup The email is what ties the anonymous visitor to a real account. How you capture it depends on the signup method. ### 1a. Email/password form Call `identify` + `track` right after the account is created: ```ts // Client-side, after your signup succeeds import { identify, track } from '@sourceloop-analytics/sdk'; identify({ email: form.email }); track('signup_completed', { method: 'password', plan: 'free_trial' }); ``` Call these **after the account actually exists**, not on submit, so a failed or rejected signup does not record a conversion. > **Do I still need this if my signup form is auto-captured?** > Yes, and it depends on where the form lives: > > - **Signup form on your marketing site** (full mode): it is auto-captured, but the explicit calls are still worth adding. They fire only on success and let you attach the plan and method. Both landing close together resolve to the **same contact** rather than two people, see [Identity stitching and dedup](/help/identity-stitching-and-dedup/). > - **Signup form inside your app** (conversions mode): auto-capture is off by design, so these calls are the **only** thing that records the signup. They are required. > > If you have not set your app up yet, do [Install SourceLoop in your app](/help/install-the-sourceloop-sdk/) first, it is what stops your **login** form from being recorded as a signup. ### 1b. Google / Facebook / GitHub OAuth signup (the important case) With OAuth, the email is only known **on your server**, in the callback, after the provider redirects back. So you bind it **server-side**, reading the visitor's id from the request cookie. This is the single most important piece for SaaS attribution. The pattern is always: ``` const anonymousId = getAnonymousId(); await sl.identify({ anonymousId, email }); await sl.track({ anonymousId, email, eventName: 'signup_completed' }); ``` `getAnonymousId` reads the `_sl_aid` cookie (set on your root domain, so it is present on `app.yoursite.com`). Concrete examples: #### NextAuth / Auth.js (App Router) ```ts // app/api/auth/[...nextauth]/route.ts import NextAuth from 'next-auth'; import { cookies } from 'next/headers'; import { Sourceloop, getAnonymousId } from '@sourceloop-analytics/sdk/server'; const sl = new Sourceloop({ websiteId: 'YOUR_WEBSITE_ID' }); // not a secret, paste it directly const handler = NextAuth({ // ...your providers... callbacks: { async signIn({ user, isNewUser }) { try { const anonymousId = getAnonymousId(cookies()); // Next App Router cookie store if (anonymousId && user.email) { // Runs on EVERY sign-in. identify is idempotent, so this is correct. await sl.identify({ anonymousId, email: user.email }); // Only on account creation — otherwise every login is a "signup". if (isNewUser) { await sl.track({ anonymousId, email: user.email, externalId: user.id, // your DB user id (optional but recommended) eventName: 'signup_completed', properties: { plan: 'free_trial' }, }); } } } catch (e) { // Never block sign-in on analytics. Log and continue. console.error('sourceloop signIn tracking failed', e); } return true; }, }, }); export { handler as GET, handler as POST }; ``` > **Guard the signup event, not the identify** > The `signIn` callback fires on **every** sign-in, not just the first one. `identify` belongs outside the guard because it is idempotent, but `track('signup_completed')` must be gated on account creation or every returning login becomes a new conversion. > > `isNewUser` is only populated when you use a NextAuth database adapter. On JWT-only setups it is `undefined`, so gate on your own check instead, such as whether you just inserted the user row, or a `createdAt` within the last few seconds. #### Clerk (webhook or a post-auth route handler) ```ts // app/api/track-signup/route.ts (call this from your client right after Clerk sign-up) import { Sourceloop, getAnonymousId } from '@sourceloop-analytics/sdk/server'; import { currentUser } from '@clerk/nextjs/server'; const sl = new Sourceloop({ websiteId: 'YOUR_WEBSITE_ID' }); // not a secret, paste it directly export async function POST(req: Request) { const user = await currentUser(); const anonymousId = getAnonymousId(req); // reads _sl_aid from the request cookies const email = user?.emailAddresses[0]?.emailAddress; if (anonymousId && email) { await sl.identify({ anonymousId, email }); await sl.track({ anonymousId, email, externalId: user!.id, eventName: 'signup_completed' }); } return Response.json({ ok: true }); } ``` #### Supabase Auth (callback route) ```ts // app/auth/callback/route.ts import { Sourceloop, getAnonymousId } from '@sourceloop-analytics/sdk/server'; import { createServerClient } from '@supabase/ssr'; import { cookies } from 'next/headers'; const sl = new Sourceloop({ websiteId: 'YOUR_WEBSITE_ID' }); // not a secret, paste it directly export async function GET(req: Request) { // ...exchange the code for a session as usual... const supabase = createServerClient(/* ... */); const { data: { user } } = await supabase.auth.getUser(); const anonymousId = getAnonymousId(req); if (anonymousId && user?.email) { await sl.identify({ anonymousId, email: user.email }); // This callback also runs on every returning OAuth login, so only record // the signup for a genuinely new account. const isNewUser = user.created_at === user.last_sign_in_at; if (isNewUser) { await sl.track({ anonymousId, email: user.email, externalId: user.id, eventName: 'signup_completed' }); } } return Response.redirect(new URL('/dashboard', req.url)); } ``` The same rule applies to every provider: **the auth callback is a login path first and a signup path only sometimes.** Whatever your stack calls it, gate `signup_completed` on account creation and leave `identify` ungated. > **`getAnonymousId` accepts whatever you have:** a Web `Request`, a `Headers` object, the Next.js `cookies()` store, a Pages-Router `req`, a raw `Cookie` header string, or a plain `{ _sl_aid: '...' }` map. Pass the request object you have in that handler. At this point you have a **signup conversion** (revenue = 0) attributed to the visitor's original marketing source, even though they signed up via OAuth. ## Step 2: The free trial (no payment yet) There is nothing extra to do for a no-card free trial. The signup conversion from Step 1 already exists and is attributed. When the user later pays, Step 3 connects the revenue to it. If you want a distinct "trial started" event (e.g. for funnel reporting), fire one: ```ts await sl.track({ anonymousId, email, eventName: 'trial_started', properties: { plan: 'pro' } }); ``` ## Step 3: Connect your payment provider Everything about revenue depends on this, so do it before you touch checkout code. Connecting the provider is what lets SourceLoop see subscriptions, renewals, and refunds, which is how a payment months from now still credits the ad that started it. You do this once, and it takes about two minutes. 1. In SourceLoop, open **Setup -> Payment** in the sidebar. Every supported provider is listed as a card: Stripe, Lemon Squeezy, Paddle, Polar, and Dodo Payments. ![SourceLoop Setup Payment page showing the supported payment provider cards](/help/screenshots/sourceloop-payment-page.png) 2. Click your provider's card. A drawer opens on the right with two tabs, **Connect webhook** and **Wire attribution**. Stay on the first tab and copy the **webhook URL**. ![SourceLoop payment provider drawer with the webhook URL and signing secret fields](/help/screenshots/sourceloop-payment-webhook-drawer.webp) 3. Paste that URL into your provider's dashboard as a new webhook endpoint, then copy the **signing secret** the provider gives you back into SourceLoop and save. The exact menu path differs per provider, so follow the one you use: | Provider | Where the webhook goes | Full guide | |---|---|---| | Stripe | Developers -> Webhooks | [Stripe setup](/help/track-lead-source-in-stripe/) | | Lemon Squeezy | Settings -> Webhooks | [Lemon Squeezy setup](/help/track-lead-source-in-lemonsqueezy/) | | Paddle | Developer Tools -> Notifications | [Paddle setup](/help/track-lead-source-in-paddle/) | | Polar | Settings -> Webhooks | [Polar setup](/help/track-lead-source-in-polar/) | | Dodo Payments | Developer -> Webhooks | [Dodo setup](/help/track-lead-source-in-dodo-payments/) | > **Without this step, revenue never appears** > Signups still attribute correctly, because those come from your own `identify` and `track` calls. But trials converting to paid, renewals, and refunds all arrive as provider events, so until the webhook is connected SourceLoop has no way to see that any money changed hands. ## Step 4: Stamp the checkout so revenue stitches back This is what connects the eventual payment to the visitor. When you create the checkout/subscription, attach the visitor's id as **metadata**. Do this **server-side** for maximum reliability. `checkoutMetadata(req)` returns `{ sourceloop_anonymous_id }`. Pass it into your provider's metadata field. ### Stripe (server-side checkout session) ```ts // app/api/create-checkout/route.ts import Stripe from 'stripe'; import { checkoutMetadata } from '@sourceloop-analytics/sdk/server'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function POST(req: Request) { const meta = checkoutMetadata(req); // { sourceloop_anonymous_id: '...' } const session = await stripe.checkout.sessions.create({ mode: 'subscription', line_items: [{ price: 'price_xxx', quantity: 1 }], success_url: 'https://app.yoursite.com/welcome', cancel_url: 'https://app.yoursite.com/pricing', metadata: meta, // CRITICAL: also stamp the subscription, so renewals carry the id too: subscription_data: { metadata: meta }, }); return Response.json({ url: session.url }); } ``` > **Always set both `metadata` and `subscription_data.metadata`.** The first attributes the initial payment; the second makes every renewal carry the id, which is what powers lifetime-value (LTV) reporting. ### Other providers (same `meta` object) ```ts const meta = checkoutMetadata(req); // { sourceloop_anonymous_id, sourceloop_id } // LemonSqueezy (checkout custom data) checkout: { custom: meta } // Paddle (Paddle.js) Paddle.Checkout.open({ items: [...], customData: meta }); // Polar / Dodo (checkout create) { metadata: meta } ``` ### If you cannot stamp metadata Attribution still works as a fallback **via email match**, because Step 1 bound that email to the visitor. It is slightly lower confidence than the metadata stitch, but it holds. Stamping the metadata is strongly preferred when you can. ## Step 5: Trial to paid and renewals (automatic) Once your payment provider is connected (Step 3), these are handled for you from the provider's webhooks, no code: - **Trial starts (card up front):** recorded with **no revenue**. - **Trial converts to paid:** the **revenue and recurring subscription** are recorded and attributed to the original click. - **Each renewal:** revenue accrues to the same original acquisition (for LTV). - **Refunds / cancellations:** reduce the recorded value / mark churn. ## Putting it together (the minimal checklist) 1. Same `websiteId` script/SDK on marketing site **and** app. 2. Connect your payment provider under **Setup, Payment**. 3. On **signup** (form or OAuth): `identify({ email })` + `track('signup_completed')`. For OAuth, do it server-side with `getAnonymousId(req)`. 4. On **subscribe**: create the checkout with `metadata: checkoutMetadata(req)` (and `subscription_data.metadata` for Stripe). 5. Done. Trials, conversions, and renewals attribute automatically. ## Common mistakes - **Calling `track('signup_completed')` on every page load or every login.** It should fire **once**, at signup. Use `identify` (idempotent) for repeated logins. - **Installing the marketing snippet unchanged inside your app.** It captures every form it sees, so invite, settings, and support forms become "leads" and in-app navigation eats your pageview allowance. Use `mode: 'conversions'` in the app. - **Doing OAuth signup attribution only on the client.** The email resolves on the server in OAuth, so do the `identify`/`track` in the callback with `getAnonymousId(req)`. - **Forgetting `subscription_data.metadata` on Stripe.** Without it, renewals are not stitched and LTV is wrong. - **Different `websiteId` on marketing vs app.** Use one id everywhere or the journey splits into two strangers. - **Blocking signup on analytics.** Wrap SourceLoop calls in try/catch so a tracking hiccup never breaks auth or checkout. ## Verify - After an OAuth signup, the user appears as a **lead/conversion** in the dashboard, with a first-touch source matching the original ad/referrer. - After a test subscription, a **subscription/payment** conversion appears, attributed to that same visitor, with revenue. - A renewal increases that customer's recorded value without creating a duplicate conversion. ## Frequently Asked Questions ### What is the difference between identify and track? identify({ email }) links a known person to their anonymous visit. It is idempotent and creates no conversion, so call it every time a user logs in or signs up. track({ eventName }) records a conversion, a meaningful event like signup_completed or trial_started, so call it once per real event, not on every page load. ### How do I attribute a Google or Facebook OAuth signup? With OAuth the email is only known on your server, in the callback. Read the visitor's id from the request cookie with getAnonymousId(req), then call sl.identify and sl.track server-side. Doing it only on the client misses the email, which is why server-side binding is the most important step for SaaS attribution. ### How does the eventual subscription revenue stitch back to the click? When you create the checkout or subscription, attach checkoutMetadata(req) (which returns sourceloop_anonymous_id) to the provider's metadata field. For Stripe, set both metadata and subscription_data.metadata so renewals carry the id too. Once your payment provider is connected under Setup, Payment, trial conversions and renewals attribute automatically from the provider webhooks. ### What if I cannot stamp checkout metadata? Attribution still works as a fallback via email match, because the signup step bound that email to the visitor. It is slightly lower confidence than the metadata stitch, but it holds. Stamping the metadata is strongly preferred when you can. --- # Cross-domain and cross-subdomain tracking Keep one visitor identity across domains, automatic for subdomains and one config line for different roots. Works for the script tag and the SDK. Source: https://sourceloop.ai/help/cross-domain-and-subdomain-tracking/ Updated: 2026-06-29 --- When a visitor moves from one site to another (marketing to app), you want SourceLoop to treat them as **one person**, not two strangers. How you do that depends on whether you are crossing **subdomains** or **different root domains**. ## First, which case are you in? | Going from → to | Example | What you need | |---|---|---| | **Subdomain → subdomain** (same root domain) | `www.yoursite.com` → `app.yoursite.com` | **Nothing.** Automatic. | | **Domain → different root domain** | `yourbrand.com` → `yourapp.io` | **One config line** (and SourceLoop on both sites). | The reason: SourceLoop stores the visitor id (`_sl_aid`) in a cookie on your **root domain** (e.g. `.yoursite.com`). Cookies are shared across subdomains, so `www.` and `app.` already see the same id. They are **not** shared across different root domains, so those need an explicit hand-off. > **Requirement for any cross-site setup:** SourceLoop must be installed on **both** sites using the **same `websiteId`**. If the two sites have different website ids, they can never be the same visitor. ## Case 1, subdomains (automatic, nothing to do) `www.yoursite.com` and `app.yoursite.com` (or `marketing.`, `dashboard.`, etc.) share identity automatically as long as both have the SourceLoop script/SDK with the **same `websiteId`**. No `crossDomains`, no special links. This is the common SaaS setup and it just works. Verify: load `www.yoursite.com`, note the `_sl_aid` cookie value (DevTools, Application, Cookies), then go to `app.yoursite.com`, the `_sl_aid` value is the same. ## Case 2, different root domains Example: marketing site on `yourbrand.com` (Webflow), app on `yourapp.io` (Next.js). Because the cookie cannot cross root domains, SourceLoop passes the id in the URL when the visitor clicks through, and the destination adopts it on arrival. You configure a list of **other domains you own** (`crossDomains`). When a visitor clicks a link from your site to one of those domains, SourceLoop appends identity params (`?sl_id=...&sl_sid=...&sl_ts=...`) to the link. The destination site (also running SourceLoop with the same `websiteId`) reads those params on load, adopts the same visitor id, and strips the params from the URL. ### 2a. Script tag (Webflow, WordPress, no-code) Add `crossDomains` to the config **on both sites**, listing the *other* domain. Replace `YOUR_WEBSITE_ID` with the **same** id on both. On `yourbrand.com` (marketing): ```html ``` On `yourapp.io` (app): ```html ``` That is it. Now when someone clicks a normal link on `yourbrand.com` that points to `yourapp.io` (e.g. your "Start free trial" button), SourceLoop automatically appends the identity params, and `yourapp.io` adopts them. You do not change your links, this happens on click. You can list multiple domains: `crossDomains: ['yourapp.io', 'docs.yourbrand.io', 'community.yourbrand.com']`. ### 2b. Coded app / SDK With the SDK, link-click decoration also works, configure `crossDomains` when bundling the tracker (via the same `SourceLoopConfig` or your init). In addition, the SDK gives you two helpers for cases the click-listener cannot catch, **programmatic navigation** (JS redirects, `window.location`, or `
` to another domain): ```ts import { getTrackingParams, buildCrossDomainUrl } from '@sourceloop-analytics/sdk'; // Easiest: build a ready-to-use URL with the params appended. window.location.href = buildCrossDomainUrl('https://yourapp.io/signup'); // → https://yourapp.io/signup?sl_id=...&sl_sid=...&sl_ts=... // Or get the raw params to attach yourself (e.g. as hidden form fields): const params = getTrackingParams(); // → { sl_id: '...', sl_sid: '...', sl_ts: '...' } ``` The destination (`yourapp.io`) must be running SourceLoop with the same `websiteId`; it adopts the params automatically on page load, no code needed there. ## How the hand-off works (so you can reason about it) 1. Visitor on `yourbrand.com` clicks a link to `yourapp.io` (a configured cross-domain). 2. SourceLoop appends `?sl_id=&sl_sid=&sl_ts=` to the destination URL. 3. On `yourapp.io`, SourceLoop reads those params **before** computing identity, adopts the same `sl_id` as the visitor id, and removes the params from the address bar. 4. Both domains now report the **same visitor**, so the marketing source carries through to the app. **Built-in safety:** the timestamp (`sl_ts`) must be within about 5 minutes, so stale/shared links cannot hijack an identity, and only valid id formats are accepted. ## Common mistakes - **Different `websiteId` on the two sites.** They can never merge. Use the same id everywhere. - **`crossDomains` on only one site.** Put it on both, and make sure the destination has SourceLoop installed so it can adopt the params. - **Expecting subdomains to need `crossDomains`.** They do not. Only different *root* domains do. Adding `crossDomains` for subdomains is unnecessary (harmless, but not needed). - **Programmatic redirects without `buildCrossDomainUrl`.** The automatic decoration only fires on real `` clicks. For `window.location = ...`, `router.push` to another domain, or form posts, use `buildCrossDomainUrl()` / `getTrackingParams()`. - **Listing a domain you do not control.** `crossDomains` should only be your own properties; the destination must run SourceLoop to adopt the id. ## Verify 1. On `yourbrand.com`, note the `_sl_aid` cookie value. 2. Click your link to `yourapp.io`. The destination URL should briefly contain `?sl_id=...` (then SourceLoop cleans it). 3. On `yourapp.io`, check the `_sl_aid` cookie, it should now match the value from `yourbrand.com`. 4. In the SourceLoop dashboard, the journey shows as a single visitor spanning both domains, with the marketing source preserved. ## Frequently Asked Questions ### Do subdomains need any cross-domain setup? No. www.yoursite.com and app.yoursite.com share identity automatically as long as both run SourceLoop with the same websiteId. The visitor id cookie (_sl_aid) is stored on your root domain, and cookies are shared across subdomains, so nothing extra is needed. Adding crossDomains for subdomains is unnecessary (harmless, but not needed). ### How do I keep one identity across two different root domains? Install SourceLoop on both sites with the same websiteId, then add a crossDomains list to the config on each site naming the other domain. When a visitor clicks a link from one site to the other, SourceLoop appends identity params to the URL and the destination adopts them on load. Put crossDomains on both sites, not just one. ### My redirect is a JS redirect or a form post, not a normal link. Does decoration still work? The automatic decoration only fires on real anchor clicks. For programmatic navigation (window.location, router.push to another domain, or a form action to another domain), use buildCrossDomainUrl(url) to get a ready URL with the params appended, or getTrackingParams() to attach them yourself as hidden fields. ### Can a shared or stale link hijack someone's identity? No. The handoff includes a timestamp (sl_ts) that must be within about 5 minutes, and only valid id formats are accepted, so stale or shared links cannot adopt an identity. --- # How to invite team members to your SourceLoop workspace Invite admins and editors to your SourceLoop workspace, scope their website access, and let them set up their account from a single email link. Source: https://sourceloop.ai/help/invite-team-members/ Updated: 2026-05-28 --- SourceLoop is built for teams. The workspace owner can invite admins, who can in turn invite editors, and every member gets scoped access to whichever websites you want them to see. Invitations go out as a single email link. The invited person clicks it, sets a password if they're new to SourceLoop, and lands in the workspace logged in. No manual provisioning, no shared logins. > **Only Admins (and the Owner) can invite teammates** > Editors don't have invite permissions. The **Add Member** button is hidden for them. If you need to add someone but only have Editor access, ask an Admin or the workspace Owner to send the invitation. ## Before you start You'll need: - A **SourceLoop workspace** with **Admin** or **Owner** access (Editors can't invite, see the warning above) - The **email address** of the person you want to invite - A rough idea of their **role** (Admin or Editor) and which websites they should have access to ## Step 1: Open the Team settings 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Settings** in the bottom of the left sidebar. ![SourceLoop Home page with the Settings entry in the left sidebar highlighted](/help/screenshots/sourceloop-settings.webp) 3. Inside Settings, click **Team** under the **Organization** group. ![SourceLoop Settings page showing the Team section with current members and the Add Member button highlighted](/help/screenshots/sourceloop-team-page.webp) You'll see every active member, every pending invitation, and an **Add Member** button in the top-right. ## Step 2: Send the invitation 1. Click **Add Member** in the top-right. The **Invite Team Member** modal opens. ![SourceLoop Invite Team Member modal with fields for email address, role, and website access](/help/screenshots/sourceloop-invite-team-member.webp) 2. Enter the **email address** of the person you want to invite. 3. Pick a **Role**: - **Admin** — full workspace control. Can invite or remove teammates, manage billing, change integrations, and access every setting. - **Editor** — can use dashboards and edit workspace data (websites, integrations, leads, conversions) but **cannot invite teammates, change roles, or touch billing**. The Add Member button is hidden for Editor accounts. 4. Pick **Website Access**: - **All websites** — they see and can act on every website in the workspace, including ones you add later. - **Selected websites** — pick specific sites to grant access to. They won't see any other workspace site in any view. 5. Click **Send Invitation**. The new member appears in the team list with a **Pending** status, and SourceLoop emails them an invitation link from your workspace. > **Need to invite five teammates at once?** > Send each invite individually for now, the modal is one-at-a-time. Bulk invite is on the roadmap. ## Step 3: The invitee accepts and sets up The person you invited receives an email titled "You've been invited to join Shivam's Organization" (or your workspace name). What they do next depends on whether they already have a SourceLoop account. ### (1) New to SourceLoop 1. They click the link in the email. 2. They land on SourceLoop with the invitation pre-filled. 3. They set a password (or sign up with Google instead, same as the regular [sign-up flow](/help/how-to-sign-up-for-sourceloop/)). 4. They land in the workspace logged in, with the role and website access you assigned. ### (2) Already has a SourceLoop account 1. They click the link in the email. 2. They sign in to their existing SourceLoop account. 3. The new workspace appears in the workspace switcher in the top-left. 4. They switch into the workspace and start working immediately. In both cases, their status on your Team page flips from **Pending** to **Active** the moment they accept. > **Invitation links expire after 7 days** > If the recipient doesn't click within a week, the link stops working. Just resend the invitation from the Team page, the previous pending invite gets replaced. ## Managing the team after invites The Team page is the home for everything ongoing: - **Edit a role** — click the edit icon on a member row to switch them between Admin and Editor. - **Change website access** — same edit icon, modify the All / Selected websites selection. - **Remove a member** — trash icon. Their access is revoked on next page load. Historical attribution stays intact. - **Cancel a pending invite** — trash icon on the pending row before they accept. - **Resend an invite** — cancel the pending invite, then send a fresh one from Add Member. Once your team is in, head back to [installing the tracking pixel](/help/install-the-tracking-pixel/) (if you haven't already) so everyone can start seeing real data. ## Frequently Asked Questions ### Can an Editor invite teammates? No. Only Admins and the Owner can invite, remove, or change roles. Editors don't see the Add Member button at all. If an Editor needs someone added, an Admin (or the Owner) has to send the invitation. ### What's the difference between Admin and Editor? Admins can manage the whole workspace, invite or remove team members, change roles, update billing, and access every setting. Editors can use the dashboards and edit workspace data (websites, integrations, leads) but can't manage team or billing. The workspace **Owner** is the original sign-up account and has full Admin permissions plus owner-only abilities like transferring the workspace. ### Can I invite the same person to two different workspaces? Yes. Invitations are scoped per workspace, so the same email can be a member of multiple workspaces with different roles in each. After accepting both invites, the user switches between workspaces from the top-left switcher. ### The invited person never received the email. What now? Ask them to check spam. SourceLoop sends from a transactional domain, but corporate spam filters sometimes catch it. If it's not in spam, re-send the invitation from the Team page (Cancel the pending invite and click Add Member again with the same email). You can also share the invitation link directly from the pending-invite row. ### Can I limit which websites a teammate can see? Yes. In the Add Member modal, pick **Selected websites** instead of **All websites** and check the boxes for the sites they should have access to. They won't see other workspace websites in any view, including the dashboards. ### How do I change someone's role after they've joined? On the Team page, click the edit icon next to the member and switch their role between Admin and Editor. The change takes effect immediately. ### How do I remove a team member? On the Team page, click the trash icon next to the member. They're removed from the workspace immediately and lose access on their next page load. Their historical activity (changes they made, leads they touched) stays attributed to them, just marked as a removed user. --- # Team member limits by plan How many team members you can invite on each SourceLoop plan, and what happens when you outgrow your seat count. Source: https://sourceloop.ai/help/team-member-limits-by-plan/ Updated: 2026-05-28 --- SourceLoop seats are tied to your plan. Every team member, the Owner included, counts toward the seat limit. When you hit it, the next invitation is blocked and SourceLoop prompts you to upgrade. This page lists the seat allotment on every plan so you know which tier to pick (or upgrade to) when your team grows. ## Individual plans These are the standard plans most teams sign up on. Public pricing lives at [sourceloop.ai/pricing](https://sourceloop.ai/pricing/). ### (1) Essential — 1 seat The starter tier, designed for a solo operator. The seat is yours (the Owner). You can't invite teammates on Essential, the Add Member button is disabled. Upgrade to Professional the moment you need to bring someone else in. - **Team seats**: 1 (the Owner) - **Websites**: 1 - **Tracked conversions**: 500 / mo ### (2) Professional — 3 seats Most-popular tier. Owner plus two teammates, mix of Admins and Editors. Typical setup: the founder is the Owner, two people split between marketing and operations. - **Team seats**: 3 - **Websites**: 3 - **Tracked conversions**: 1,500 / mo ### (3) Business — Unlimited seats For mid-market teams. Invite anyone in the company, no cap. Scoped website access (in the [invite flow](/help/invite-team-members/)) becomes useful here so marketers only see the websites they own. - **Team seats**: Unlimited - **Websites**: Unlimited - **Tracked conversions**: 5,000 / mo ## Enterprise For teams with custom volume, security, or compliance needs. - **Team seats**: Unlimited (effectively, the limit is set on a per-contract basis) - **Websites**: Unlimited - **Tracked conversions**: Custom [Book a demo](/help/book-a-demo/) if you're looking at Enterprise, we'll scope the limits to your team and plug you in. ## When you hit the seat limit The Add Member button still works on plans where you have seats remaining. When you've used all of them, clicking it shows a "Seat limit reached" prompt with an Upgrade button. To free up a seat without upgrading, remove an existing member from the Team page (see [Invite team members](/help/invite-team-members/) for managing the team). Removed members lose access immediately. Their historical activity stays attributed to them. ## How to upgrade 1. Open **Settings -> Billing** in the left sidebar. 2. Click **Change plan** or **Upgrade** on your current plan card. 3. Pick the tier that matches the seat count you need (Professional for 3 seats, Business for unlimited). 4. Confirm the prorated charge for the current billing period. Your new seat allowance is active immediately, you can return to **Settings -> Team** and send the next invitation right away. ## Frequently Asked Questions ### Does the Owner count toward the seat limit? Yes. The Owner is always a team member, so on Essential (1 user) the Owner is the one seat. On Professional (3 users) the Owner counts as one of the three. ### Do pending invitations count toward the seat limit? Yes. The moment you click **Send Invitation**, the seat is reserved. If the invite isn't accepted within 7 days the seat frees up again automatically. ### What happens if I try to invite past my seat limit? SourceLoop blocks the invitation with a "Seat limit reached" message and prompts you to upgrade. Existing members keep working as normal. ### Can I add extra seats without upgrading the whole plan? Not today. Seat counts are tied to the plan tier (Essential, Professional, Business). To add seats, you upgrade to the next tier. The price difference is prorated for the current billing period. ### Are there separate roles within the seat count? No. Every seat counts the same whether the member is an Admin, Editor, or the Owner. See [Invite team members](/help/invite-team-members/) for the difference between roles. --- # SourceLoop pricing and plans A walkthrough of SourceLoop's three plans, what's included on each, how billing works, and how to pick the right tier. Source: https://sourceloop.ai/help/pricing-and-plans/ Updated: 2026-05-28 --- SourceLoop has three plans, designed around the natural growth path of a marketing-attribution stack: one for solo operators, one for growing teams, one for mid-market and beyond. All three include the full feature set, multi-touch attribution, custom dashboards, funnels, and every integration. The tiers differ on volume (websites, conversions, events, team seats) and on which ad platforms you can sync conversions to. Public pricing lives at [sourceloop.ai/pricing](https://sourceloop.ai/pricing/), and the same plan cards are mirrored on the in-app billing page once you sign in. ## Plan overview ### (1) Essential — $49 / month The solo tier. Designed for one operator, one website, an indie founder or a marketer running a single brand. - **Team seats**: 1 (just you) - **Websites**: 1 - **Tracked conversions**: up to 500 / month - **Tracked events**: up to 150,000 / month - **Integrations**: every form, meeting, chat, and payment tool - **Dashboards & funnels**: full access - **Ad platform connections (Google Ads, Meta, LinkedIn)**: not included - **Auto-sync conversions to ad networks**: not included Annual billing brings the price down to $37 / month (saves $144 / year). ### (2) Professional — $99 / month The most-popular tier. The starting point for teams that have outgrown a single seat and are spending on paid ads. The auto-sync to Google / Meta / LinkedIn kicks in at this tier, which is the headline feature for paid-marketing teams. - **Team seats**: 3 - **Websites**: 3 - **Tracked conversions**: up to 1,500 / month - **Tracked events**: up to 500,000 / month - **Integrations**: every form, meeting, chat, and payment tool - **Ad platform connections**: Google Ads, Meta, LinkedIn included - **Auto-sync conversions to ad networks**: included Annual billing brings the price down to $74 / month (saves $300 / year). ### (3) Business — $249 / month For mid-market teams and agencies running multiple websites or brands. Unlimited seats and websites, higher conversion and event caps. - **Team seats**: Unlimited - **Websites**: Unlimited - **Tracked conversions**: up to 5,000 / month - **Tracked events**: up to 5,000,000 / month - **Integrations**: every form, meeting, chat, and payment tool - **Ad platform connections**: Google Ads, Meta, LinkedIn included - **Auto-sync conversions to ad networks**: included Annual billing brings the price down to $187 / month (saves $744 / year). ### Enterprise For teams that need custom volume, security review, SAML / SSO, a signed DPA, or a dedicated success contact. [Book a demo](/help/book-a-demo/) to scope an Enterprise contract. ## How billing works SourceLoop bills through [Polar](https://polar.sh/), our merchant of record. Polar handles the checkout, payment processing, regional VAT and sales-tax compliance, and stores your card on file. SourceLoop itself never sees your card details. When you pick a plan from the in-app billing page (**Settings -> Billing**), you're redirected to a Polar checkout page with the plan pre-selected. After checkout, your subscription is active immediately and you're returned to SourceLoop. Every receipt and invoice comes from Polar. > **Tax-inclusive pricing in the EU and UK** > Polar handles VAT for you. The price you see is what you pay, the VAT is added at checkout (or reverse-charged for B2B customers with a valid VAT number). ## Picking the right plan A rough guide: - **Solo founder, single brand** → Essential - **Marketing team of 2-3, running paid ads** → Professional - **Multiple brands / sites, or a larger team** → Business - **You need SSO / DPA / custom contract** → Enterprise If you're unsure between two tiers, start lower. Upgrading is one click (see [Change your plan](/help/change-your-plan/)) and pro-rated for the current billing period. ## Trial details Every new SourceLoop account starts with a **7-day free trial of the Professional plan**. No credit card is asked at sign-up, the trial just starts running. At the end of seven days the trial ends; your data is preserved, but tracking pauses until you pick and activate a plan. The article [Activate a subscription after the trial](/help/activate-subscription-after-trial/) walks through what to do at trial end. ## What you get on every plan Some things are included regardless of tier, no upsell wall in front of the core product: - Multi-touch attribution with **7 attribution models** (last-touch, first-touch, linear, position-based, time-decay, last-non-direct, first-non-direct) - Custom dashboards and the pre-built attribution report - Funnel builder - Contacts Hub with revenue per contact - Every form, meeting, chat, and payment integration - 25% discount on annual billing - Polar-handled tax compliance The differences live in the volume and ad-sync features, not in the depth of the product. ## Frequently Asked Questions ### Is there a free trial? Yes. Every new account starts with a 7-day free trial of the Professional plan. No credit card is needed to start the trial, and your account isn't charged anything until you actively pick a plan and check out. ### What's the difference between monthly and annual billing? Annual billing saves 25% on every plan. You pay once for the full year and your account is renewed yearly. Monthly billing is the same plan, billed every 30 days, with no discount. ### Do you charge by event or by lead? Both, separately. Each plan has a tracked-events cap (page views, sessions) and a tracked-conversions cap (form submissions, meetings, chats, payments). The events cap is normally the looser of the two, conversions are what most teams hit first. ### What's a "tracked conversion"? Anything attributable, a form submission, a meeting booked, a chat opened with email captured, a Stripe / Lemon Squeezy / Paddle / Polar / Dodo payment received. Repeat conversions from the same contact in the same billing month count once each. ### What happens if I go over the conversion or event cap? SourceLoop keeps tracking, your data is never dropped. But your account is flagged for an upgrade. We email you and prompt you in-app to move to the next tier. If you go over by a small amount, we don't auto-charge or block you. ### Are there discounts for non-profits / open source / students? Yes, on a case-by-case basis. Email hello@sourceloop.ai with a one-line description of your situation, we'll send you a discount code if it's a fit. --- # Activate a subscription after the free trial Pick a plan, check out through Polar, and keep your workspace, data, and integrations running once the 7-day free trial ends. Source: https://sourceloop.ai/help/activate-subscription-after-trial/ Updated: 2026-05-28 --- Every new SourceLoop account gets a 7-day free trial of the Professional plan, no card asked. When the trial ends, tracking pauses until you pick a plan and check out. This guide walks through the activation in three steps. The whole flow takes around two minutes. Polar handles the payment, so the card form is theirs, not ours. ## Before you start You'll need: - A **SourceLoop account** that's been on a trial (you can activate before or after the trial ends, either works) - A **payment method** (credit / debit card, or whatever Polar supports in your region) - A rough idea of which plan you want, see [Pricing and plans](/help/pricing-and-plans/) for the breakdown ## Step 1: Open the Billing page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Settings** in the bottom of the left sidebar. ![SourceLoop Home page with the Settings entry in the left sidebar highlighted](/help/screenshots/sourceloop-settings.webp) 3. Inside Settings, click **Billing** under the **Organization** group. ![SourceLoop Settings Billing page with Subscription and Billing section, Manage Subscription button, and Essential, Professional, Business plan cards](/help/screenshots/sourceloop-billing-page.webp) The page shows your current subscription state (during a trial, you'll see a banner that the trial is active and when it ends) and three plan cards: **Essential**, **Professional**, **Business**. ## Step 2: Pick a plan and billing cycle 1. Toggle **Monthly billing** or **Annual billing** at the top of the plan cards. Annual saves 25%. 2. Scan the three plan cards. For a recap of what's included on each, see [Pricing and plans](/help/pricing-and-plans/). 3. Click **Get Started** on the plan you want. You're redirected to **Polar** with the plan and billing cycle pre-filled. > **Most teams start on Professional** > Professional is the most-picked tier because it includes the ad-platform connections (Google Ads, Meta, LinkedIn) plus auto-sync of conversions back to those networks. Essential covers the attribution basics; Business is for teams that need unlimited seats or websites. ## Step 3: Check out on Polar Polar opens in a new page with your plan and total pre-filled. 1. Enter your **billing email** (defaults to your SourceLoop email; you can change it). 2. Enter your **card details**. 3. For business customers in the EU / UK, enter your **VAT number** to reverse-charge tax. 4. Click **Subscribe**. Polar processes the payment, emails you a receipt, and redirects you back to SourceLoop. Your subscription is **Active** the moment you land back in the app, no waiting, no manual confirmation step. If you were past your trial, tracking resumes immediately. New page views, conversions, and integrations all start firing again. ## What you can do next - **Manage your subscription** (update card, see invoices, cancel) — click **Manage Subscription** on the Billing page. It takes you to the Polar customer portal. See [Update your payment method](/help/update-your-payment-method/) for details. - **Switch plans later** — Upgrade or Downgrade buttons on the Billing page change the plan in one click. See [Change your plan](/help/change-your-plan/). - **Bring in your team** — Settings -> Team. See [Invite team members](/help/invite-team-members/). If anything went wrong at checkout (declined card, region-restriction, etc.), email hello@sourceloop.ai with the exact error message from Polar and we'll help sort it. ## Frequently Asked Questions ### Will SourceLoop auto-charge me when my trial ends? No. SourceLoop never charges a card you haven't entered. The trial ends, tracking pauses, and your data is preserved. To resume tracking, you actively pick a plan and check out via Polar. ### What happens to my data if I don't activate by the time the trial ends? Everything you've collected stays in your workspace, contacts, journeys, conversions, integrations, dashboards. Nothing is deleted. The only thing that pauses is new tracking. Once you activate, capture resumes immediately on your next page load. ### How long do I have to activate? Indefinitely. Your trial workspace doesn't expire just because the trial does. You can come back weeks or months later, pick a plan, and pick up where you left off. ### Why Polar instead of just collecting cards directly? Polar is our merchant of record. They handle VAT/sales-tax compliance, payment processing across countries, fraud detection, and invoices. Using a merchant of record means SourceLoop doesn't store your card details and you get a tax-compliant invoice for every charge automatically. ### Do I need to re-install the tracking pixel after activating? No. The pixel keeps the same install across trial and paid. It paused during the trial-ended state and starts capturing again the moment your subscription is active. ### Can I switch billing cycle (monthly vs annual) at checkout? Yes. On the billing page in SourceLoop, toggle **Monthly billing** or **Annual billing** before clicking Get Started. Whatever you pick is what Polar pre-fills on the checkout. You can change it later by switching to a different plan, see [Change your plan](/help/change-your-plan/). --- # Change your SourceLoop plan Upgrade or downgrade between Essential, Professional, and Business in one click. Pro-rated charges are handled automatically by Polar. Source: https://sourceloop.ai/help/change-your-plan/ Updated: 2026-05-28 --- Plan changes happen instantly. Click the Upgrade or Downgrade button on the plan card you want, confirm on Polar, and the new limits and features apply on the next page load. Polar handles the pro-rated charge or credit automatically. ## Before you start You'll need: - An **active SourceLoop subscription** (changing plans isn't available during the trial; activate first via [Activate a subscription after the trial](/help/activate-subscription-after-trial/)) - **Admin** or **Owner** access on the workspace (Editors can't change billing) ## Step 1: Open the Billing page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Settings** in the bottom of the left sidebar. ![SourceLoop Home page with the Settings entry in the left sidebar highlighted](/help/screenshots/sourceloop-settings.webp) 3. Inside Settings, click **Billing**. ![SourceLoop Settings Billing page showing the current plan, Manage Subscription, and Essential, Professional, Business plan cards with Upgrade and Downgrade buttons](/help/screenshots/sourceloop-billing-page.webp) You'll see your current plan at the top (with the next billing date) and three plan cards underneath. The button on each card reflects what changing to that plan means relative to where you are now, **Upgrade**, **Downgrade**, or your current plan (no action available). ## Step 2: Pick the new plan 1. Toggle **Monthly billing** or **Annual billing** if you're also switching billing cycle. Annual saves 25%. 2. Click **Upgrade** (or **Downgrade**) on the plan card you want. You're redirected to **Polar** with the new plan pre-selected. > **Switching from monthly to annual** > Treat this as a plan change. Pick the same tier you're on but with the annual toggle enabled. Polar pro-rates the difference and applies the 25% annual discount immediately. ## Step 3: Confirm on Polar Polar shows you the prorated charge (for upgrades) or credit (for downgrades) before you confirm. 1. Review the new plan, billing cycle, and prorated amount. 2. Confirm using the card already on file (Polar remembers it from your previous checkout). 3. Click **Confirm change**. That's it. You're returned to SourceLoop, the plan change takes effect on the next page load, and your invoice / receipt arrives from Polar by email within a few minutes. ## What the change actually affects For an **upgrade**, you get higher caps and more features (seats, websites, conversions, ad-network connections) immediately. Anything you've collected so far stays attached to the workspace. For a **downgrade**, the new caps apply immediately. If you're already past the lower cap (more seats, more conversions, etc.), see the FAQ above for what happens. Short version: nothing is deleted; new captures of the over-capacity type pause until your next billing cycle. ## When the change is denied A couple of edge cases will block the change at the Polar step: - **Card declined / authorisation failed**: the prorated upgrade charge couldn't be captured. Update your payment method via [Update your payment method](/help/update-your-payment-method/), then retry the upgrade. - **Past-due invoice**: a previous invoice failed. Resolve that first via the Manage Subscription button, then change plans. - **Region restriction**: rare, but some payment methods aren't supported in every country. Email hello@sourceloop.ai if you hit this and we'll help. ## Frequently Asked Questions ### What happens to my data when I downgrade? Nothing. Your data (contacts, journeys, dashboards, integrations) stays intact whether you upgrade or downgrade. Downgrading lowers the seat / website / conversion caps, but anything that was already captured remains accessible. ### I downgraded and I'm now over the new limit. What happens? SourceLoop doesn't delete data. It flags the account as over-limit and pauses new captures of the type that's over. For example, if you downgrade to Essential and you currently have 800 tracked conversions for the month, the next conversions in that month won't be recorded until the next billing cycle reset (or until you upgrade back). ### Does upgrading or downgrading cost extra outside the new monthly price? No. Polar pro-rates the change automatically. If you upgrade mid-cycle, you're charged the prorated difference for the rest of the period. If you downgrade mid-cycle, the unused portion is credited to your account and applied to the next invoice. ### How fast does the new plan take effect? Immediately. The moment you confirm the change on Polar, the new plan's limits and features are active. There's no waiting for the next billing cycle. ### Can I switch billing cycle (monthly to annual or back) without changing the tier? Yes. Change your plan on the Billing page, but pick the same tier on the toggle you want (monthly or annual). Polar treats this as a plan change and handles the proration the same way. ### I want to move from a paid plan back to a free / trial state. Is that possible? No. SourceLoop is free for the 7-day trial only; after that, you're on a paid plan or your subscription is cancelled. See [Cancel your subscription](/help/cancel-your-subscription/) for the cancel flow if you want to stop paying entirely. --- # Update your payment method Add, change, or replace the card on file for your SourceLoop subscription from the Polar customer portal, without waiting on support. Source: https://sourceloop.ai/help/update-your-payment-method/ Updated: 2026-05-28 --- Your card on file lives with Polar, not with SourceLoop. Updating it is a one-click handoff from the SourceLoop Billing page into the Polar customer portal, then a card-replace form on Polar's side. The actual update takes about a minute. ## Before you start You'll need: - An **active SourceLoop subscription** (you can't update payment during a trial because there's no card on file yet, activate first via [Activate a subscription after the trial](/help/activate-subscription-after-trial/)) - **Admin** or **Owner** access on the workspace (Editors can't manage billing) - The **new card details** ready (number, expiry, CVV, billing address) ## Step 1: Open the Billing page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Settings** at the bottom of the left sidebar. ![SourceLoop Home page with the Settings entry in the left sidebar highlighted](/help/screenshots/sourceloop-settings.webp) 3. Click **Billing** in the Settings sidebar. ![SourceLoop Billing page showing the current plan, Manage Subscription button, and plan cards](/help/screenshots/sourceloop-billing-page.webp) You'll see your current subscription card at the top with a **Manage Subscription** button in the top-right. ## Step 2: Open the Polar customer portal 1. Click **Manage Subscription** on the current-plan card. 2. A new tab opens, this is the **Polar customer portal**, where every billing action lives (payment method, invoices, cancellation). Polar is our payment processor. SourceLoop hands you off to it for any change to your actual payment, your card, your invoices, and your cancellation flow are all owned by Polar. ## Step 3: Update the card on Polar 1. In the Polar customer portal, look for the **Payment method** section (sometimes labeled "Billing"). 2. Click **Update payment method** (or **Replace card**, depending on Polar's current UI). 3. Enter the new card details: number, expiry, CVV, and billing address / postal code. 4. Click **Save** or **Update**. The new card replaces the old one immediately. The next renewal charge, plus any pending failed retries, will use the new card. > **If your last payment failed** > Updating the card automatically triggers Polar to retry the most recent failed charge with the new card. If the retry succeeds, your subscription flips back to **Active** within a few minutes. Refresh the SourceLoop Billing page to confirm. ## What happens after the update - **Active subscription**: nothing visible changes. The next renewal charges the new card. - **Past-due subscription**: Polar retries the failed charge. If it succeeds, you're back to Active. If it fails again, you'll get an email from Polar to try a different card. - **Subscription in trial**: there shouldn't be a card on file yet. Activate first via [Activate a subscription after the trial](/help/activate-subscription-after-trial/). You don't need to do anything inside SourceLoop after the Polar update, the change syncs back automatically. ## Frequently Asked Questions ### My card is about to expire. What should I do? Update it before the next billing date to avoid a failed payment. Polar emails you a reminder about a week before a card on file expires, but you can update it any time via the Manage Subscription button. ### My last payment failed. What now? Polar retries automatically a few times over several days. To resolve it faster, click Manage Subscription on the Billing page and update the card. The next retry will use the new card. If you wait too long, your subscription is paused, but no data is deleted, see [Cancel your subscription](/help/cancel-your-subscription/) for what a paused / cancelled state means. ### Can I store multiple payment methods? Polar keeps one card on file as the default. Replacing the card replaces the default. There's no concept of a backup card today. ### Does SourceLoop see my card details? No. The card lives entirely with Polar (our merchant of record). SourceLoop just sees the subscription status (active, past-due, cancelled) and the plan you're on. No card numbers, CVVs, or expiration dates touch our infrastructure. ### Can I pay by bank transfer / ACH / invoice instead of a card? For Essential, Professional, and Business plans, the answer is card-only via Polar's standard checkout. For Enterprise contracts, we can arrange invoice / bank transfer / annual prepay. [Book a demo](/help/book-a-demo/) to scope an Enterprise contract. --- # View and download SourceLoop invoices See every payment on your SourceLoop account, find a specific invoice, and download tax-compliant PDFs from the Polar customer portal. Source: https://sourceloop.ai/help/view-and-download-invoices/ Updated: 2026-05-28 --- SourceLoop's invoicing lives entirely with Polar, our merchant of record. Every charge generates a tax-compliant invoice that you can view or download from the Polar customer portal. SourceLoop's job is just to hand you off to the right place. The flow takes about thirty seconds end-to-end. ## Before you start You'll need: - An **active SourceLoop subscription** (trial accounts have no invoices yet, since there's nothing charged) - **Admin** or **Owner** access on the workspace (Editors can't access billing) ## Step 1: Open the Billing page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Settings** at the bottom of the left sidebar. ![SourceLoop Home page with the Settings entry in the left sidebar highlighted](/help/screenshots/sourceloop-settings.webp) 3. Click **Billing** in the Settings sidebar. ![SourceLoop Billing page with the current subscription card and the Manage Subscription button in the top-right](/help/screenshots/sourceloop-billing-page.webp) The current-plan card shows the next billing date and a **Manage Subscription** button. ## Step 2: Open the Polar customer portal 1. Click **Manage Subscription** on the current-plan card. 2. A new tab opens. This is the **Polar customer portal**, where every invoice, receipt, and billing record lives. ## Step 3: Find and download the invoice 1. In the Polar customer portal, look for the **Invoices** or **Billing history** section. 2. Each row shows the invoice date, amount, status (Paid / Pending / Failed), and a download icon. 3. Click the download icon (or **Download PDF**) next to the invoice you want. The PDF includes: - Polar's company details and VAT registration (as merchant of record) - Your company / billing details - The plan and billing period covered - Itemised subtotal, tax, and total - Payment method (last 4 of the card) - Invoice number for your accounting records > **Need a different billing address on future invoices?** > Inside the Polar portal, update the billing details on your account. Every invoice from that point forward uses the new details. To change details on past invoices, see the FAQ above. ## Where invoices also show up - **In your email** — Polar emails the billing email on file every time a charge succeeds. The email includes a PDF receipt. - **In SourceLoop** — the Billing page doesn't list every invoice (that's by design, the source of truth is Polar). But the **Next billing date** on the current-plan card tells you when the next invoice will be issued. If a teammate needs invoices but doesn't have Admin access, the cleanest path is to set the Polar billing email to a finance team alias so the receipts go straight to them automatically. ## Frequently Asked Questions ### Are SourceLoop invoices tax-compliant for the EU / UK? Yes. Polar is our merchant of record and handles VAT compliance automatically. Every invoice includes the correct VAT line, your VAT number (if entered), Polar's VAT registration, and the reverse-charge note where it applies. You can hand them to your accountant as-is. ### Where are my US sales-tax receipts? Same place. Polar handles US sales tax on a state-by-state basis where applicable. The invoice shows the tax line if any was charged. ### Can I get invoices for past months I've forgotten to download? Yes. Polar keeps every historical invoice in your customer portal. Scroll back as far as you need. ### Can I change the billing details on past invoices (company name, address, VAT)? Polar can update the billing details on your account and reissue future invoices with the new details. Reissuing past invoices with retroactive details is harder, in most cases the easiest path is to add a credit note. Email hello@sourceloop.ai with the specific situation and we'll loop in Polar. ### I want invoices emailed automatically to my accountant. Possible? Polar emails the primary billing email at every charge. Set the billing email to a finance / accounting alias if you want one automatic place that gets every invoice. You can update the billing email inside the Polar customer portal. ### Is there a separate invoice per website if I have multiple? No. Your SourceLoop subscription is per workspace, not per website. One invoice covers the whole workspace each billing period regardless of how many websites you have. --- # Cancel your SourceLoop subscription Stop your SourceLoop subscription via the Polar customer portal, with what happens to your data, your access, and the billing period after cancel. Source: https://sourceloop.ai/help/cancel-your-subscription/ Updated: 2026-05-28 --- Cancellation runs through the Polar customer portal, same as every other payment-method change. SourceLoop hands off to Polar, you confirm there, and the cancellation takes effect at the end of your current paid period. Your data isn't lost the second you cancel. Cancelled workspaces stay in a read-only state for 30 days after the billing period ends, with a route to reactivate if you change your mind. ## Before you start You'll need: - An **active SourceLoop subscription** (trial accounts can just stop using the product, no cancellation needed) - **Admin** or **Owner** access on the workspace (Editors can't manage billing) - A clear head about the date math: cancelling now means **access continues until the end of the current billing period** ## Step 1: Open the Billing page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Settings** at the bottom of the left sidebar. ![SourceLoop Home page with the Settings entry in the left sidebar highlighted](/help/screenshots/sourceloop-settings.webp) 3. Click **Billing** inside Settings. ![SourceLoop Billing page showing the current subscription card with the Manage Subscription button in the top-right](/help/screenshots/sourceloop-billing-page.webp) The current-plan card shows your plan, status, next billing date, and a **Manage Subscription** button. ## Step 2: Open the Polar customer portal 1. Click **Manage Subscription** on the current-plan card. 2. A new tab opens to the **Polar customer portal**. Polar owns the cancellation flow (and every other billing action) because they're the merchant of record on the subscription. ## Step 3: Cancel on Polar 1. In the Polar portal, look for the **Cancel subscription** link or button (sometimes inside a "Manage plan" section). 2. Polar asks for a confirmation and may ask why you're cancelling (optional feedback). 3. Confirm the cancellation. Polar shows you the **effective cancellation date** — that's the end of your current paid billing period, not the moment you click cancel. > **Before you cancel, consider downgrading** > If the price is the concern, downgrading to a cheaper plan keeps your data and integrations alive without losing the workspace. See [Change your plan](/help/change-your-plan/) for the downgrade flow. ## What happens between cancel and end-of-period Nothing changes. Until the date Polar showed you on cancellation: - Your subscription stays **Active** - All tracking continues - All team members keep their access - Every integration keeps firing Use this window to export anything you need (CSVs from the Contacts Hub, dashboard screenshots, integration configs) if you don't intend to reactivate. ## What happens after the cancellation date At the end of the billing period: - Your workspace flips to **Read-only** - Tracking pauses, no new conversions or events are captured - The Billing page surfaces a **Reactivate** button on the current-plan card During this read-only window (30 days), every Admin can still sign in, view dashboards, view contacts, and export data. The only thing they can't do is capture new data or change integrations. After 30 days of read-only state with no reactivation, the workspace is queued for permanent deletion. Once deleted, the data can't be restored, no backups, no recovery path. ## Reactivating before deletion If you change your mind during the read-only window: 1. Sign back in to [SourceLoop](https://app.sourceloop.ai/). 2. Go to **Settings -> Billing**. 3. Pick a plan (same as the [activate flow](/help/activate-subscription-after-trial/)). 4. Check out on Polar. Your workspace flips back to **Active**, tracking resumes on the next page load, and every dashboard, contact, integration, and lead comes back in place. If you have questions about exporting before cancel or want to delete immediately instead of using the read-only window, email hello@sourceloop.ai. ## Frequently Asked Questions ### Will my data be deleted when I cancel? Not immediately. Cancelled accounts stay in read-only mode for 30 days after the current billing period ends, so you can come back, log in, export anything you need, and reactivate if you change your mind. After 30 days of read-only state with no reactivation, the workspace is queued for permanent deletion. ### Can I reactivate after cancelling? Yes, until your data is permanently deleted (30 days after the cancellation-end date). Just sign back in and pick a plan from the Billing page. Your dashboards, contacts, integrations, and historical attribution all come right back. ### Do I get a refund for the unused part of the billing period? No. Cancelling stops the next renewal but doesn't refund the current period. Your access continues until the end of the period you've already paid for. If you cancel monthly mid-month, you keep access until the end of that month. If you cancel annually mid-year, you keep access until the next yearly renewal date. ### Is there a difference between "cancel" and "delete my account"? Yes. **Cancel** stops the subscription, you keep read-only access for 30 days after the billing period ends. **Delete my account** is a separate request to wipe everything immediately. Email hello@sourceloop.ai to request a full account deletion, this is irreversible. ### What about my team members after I cancel? Every member loses dashboard access at the same time you do (end of the paid period). The read-only window covers the workspace, so any Admin can sign in and export until the 30-day grace period expires. ### Can I pause my subscription instead of cancelling outright? Not today. The two options are cancel (next renewal stops) or downgrade (move to a lower tier). If you need a true pause, email hello@sourceloop.ai with the context and we'll look at it case-by-case. --- # Section: Marketing attribution tracking Track web form submissions, meetings, phone calls, live chats, and payments back to the original marketing source that produced each one. # How SourceLoop classifies marketing channels How SourceLoop classifies a visit as Paid Search, Organic, Paid Social, Email, Referral, or Direct, the signals it reads, and how to override them. Source: https://sourceloop.ai/help/channel-definitions/ Updated: 2026-05-29 --- A **channel** in SourceLoop is the highest-level marketing source bucket: Paid Search, Organic Search, Paid Social, Organic Social, Email, Referral, Direct, and a few others. Every visitor session, conversion, and dollar of revenue rolls up to a channel before it rolls up to anything more detailed (source, medium, campaign). Understanding how SourceLoop decides which channel a visit belongs to is the difference between trusting your dashboard and quietly second-guessing it. This article covers the default channels, the signals SourceLoop reads, the order it reads them in, and where the common edge cases land. ## The default channels SourceLoop ships with a curated channel grouping that mostly mirrors Google Analytics 4's standard channels, with a few additions and one philosophical difference (click IDs take priority over referrers, which narrows the Direct bucket). | Channel | What lands here | |---|---| | **Paid Search** | Clicks on paid search ads, Google Ads, Microsoft Ads, Yahoo, DuckDuckGo. Detected by `gclid`, `msclkid`, `gbraid`, `wbraid` on the URL, or `utm_medium=cpc / ppc / paidsearch` | | **Organic Search** | Unpaid clicks from search engines. Detected by referring domain (google.com, bing.com, duckduckgo.com, etc.) with no paid click ID present | | **Paid Social** | Clicks on paid ads on social platforms, Meta (Facebook + Instagram), LinkedIn, TikTok, Pinterest, Reddit, X. Detected by `fbclid`, `li_fat_id`, `ttclid`, `epik`, `rdt_cid`, or `utm_medium=paidsocial / cpc + utm_source=facebook/linkedin/tiktok/etc.` | | **Organic Social** | Unpaid clicks from social platforms. Detected by referring domain (facebook.com, linkedin.com, twitter.com, reddit.com, youtube.com, etc.) without a paid click ID, or `utm_medium=social` | | **Email** | Detected by `utm_medium=email / newsletter` or a referring domain from a webmail provider (gmail.com, outlook.com, yahoo.com mail) | | **Referral** | Detected by any external referring domain that's not a search engine, social platform, or email client. A blog post linking to you, a docs site, a community wiki, etc. | | **Display** | Detected by `utm_medium=display / banner / cpm` or a known display ad network | | **Affiliate** | Detected by `utm_medium=affiliate / partner` | | **SMS** | Detected by `utm_medium=sms` | | **Push** | Detected by `utm_medium=push / notification` | | **Audio** | Detected by `utm_medium=audio / podcast` | | **Paid Shopping** | Detected by Google / Microsoft shopping ad click IDs or `utm_medium=cpc + utm_source=google_shopping` | | **Organic Shopping** | Detected by `utm_medium=organic_shopping` or referrals from shopping comparison sites | | **Direct** | Fallback. No referrer, no UTMs, no click IDs. Bookmarks, typed URLs, in-app browser opens, iOS apps that strip the referrer, etc. | ## The order SourceLoop reads signals When a visitor lands on your site, SourceLoop walks through these signals in order. The first one that produces a confident answer wins: 1. **Paid click IDs** — `gclid`, `msclkid`, `fbclid`, `li_fat_id`, `ttclid`, `epik`, `rdt_cid`, `gbraid`, `wbraid`. These are the strongest signal. A `gclid` on the URL means Google Ads, full stop, even if the referrer is missing and the UTMs are absent. 2. **`utm_medium` + `utm_source`** — explicit UTM tagging. `utm_medium=cpc&utm_source=google` → Paid Search. 3. **Referring domain** — matched against a curated list of search engines, social platforms, email clients, shopping sites, and known referrers. `referrer=google.com&no UTMs&no click ID` → Organic Search. 4. **Direct fallback** — when none of the above produce a hit. No referrer, no UTMs, no click IDs. This order matters because it lets click IDs and explicit UTMs rescue traffic that would otherwise look Direct or Referral. For example: a visitor clicks a Google Ad on iOS Safari (which strips the referrer for privacy). Without click IDs, the visit would be Direct. With the `gclid` on the URL, SourceLoop confidently classifies it as Paid Search. ## How each signal is detected ### Paid click IDs | Click ID | Set by | Channel | |---|---|---| | `gclid` | Google Ads (auto-tagging on) | Paid Search (or Paid Shopping for product-listing ads) | | `gbraid`, `wbraid` | Google Ads (iOS, app campaigns) | Paid Search | | `msclkid` | Microsoft Ads | Paid Search | | `fbclid` | Meta (Facebook + Instagram) ads | Paid Social | | `li_fat_id` | LinkedIn ads | Paid Social | | `ttclid` | TikTok ads | Paid Social | | `epik` | Pinterest ads | Paid Social | | `rdt_cid` | Reddit ads | Paid Social | Click IDs are appended to the landing URL by the ad platform's auto-tagging feature, which is on by default in most accounts. If you've turned it off, none of these click IDs will appear, falling back to UTM parameters or referrer matching. See [Click IDs explained](/help/click-ids-explained/) for the full reference per platform. ### UTM parameters The standard five UTM parameters (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`) feed channel classification through `utm_medium` plus `utm_source`. The medium typically determines the channel; the source determines the exact ad platform within it. | `utm_medium` value | Channel | |---|---| | `cpc`, `ppc`, `paidsearch` | Paid Search | | `paidsocial`, `cpm + social platform source` | Paid Social | | `social` | Organic Social | | `email`, `newsletter` | Email | | `display`, `banner`, `cpm` | Display | | `affiliate`, `partner` | Affiliate | | `referral` | Referral | | `sms` | SMS | | `push`, `notification` | Push | | `audio`, `podcast` | Audio | | `organic` | Organic Search (when source matches a search engine) | See [UTM best practices](/help/utm-best-practices/) for tagging conventions that play well with this default mapping. ### Referring domain When no click ID and no UTMs are present, SourceLoop matches the `document.referrer` against a curated lookup table: | Referrer domain example | Channel | |---|---| | google.com, bing.com, duckduckgo.com, yahoo.com | Organic Search | | facebook.com, instagram.com, linkedin.com, twitter.com, x.com, tiktok.com, youtube.com, reddit.com | Organic Social | | gmail.com, outlook.com, mail.yahoo.com | Email | | pinterest.com (organic), pinterest.ca, pinterest.co.uk | Organic Social | | Any other external domain | Referral | The lookup table is curated by the SourceLoop team and updated as new search engines and social platforms appear. ### Direct fallback Direct catches everything the above three signals miss: bookmarks, typed URLs, `` clicks, iOS app webviews (Instagram, TikTok, Facebook, X in-app browsers all strip the referrer by default), enterprise VPN proxies that strip referrers, and ad blockers that strip query strings. A high share of Direct traffic is one of the strongest signals that your paid campaigns aren't tagged thoroughly enough, see the first FAQ for the common causes. ## Custom channel rules You can override the defaults with custom channel rules: - **Promote a source** — `utm_source=newsletter` → custom "Newsletter" channel instead of generic Email - **Split a channel** — Paid Social → "Meta Ads", "LinkedIn Ads", "TikTok Ads" as separate channels - **Bucket referrers** — `pinterest.com` referrals → custom "Pinterest" channel separate from Organic Social - **Match by landing page** — visits to `/partners/...` → "Partner Program" channel Rules are evaluated before the defaults; first match wins. Set them at **Setup -> Channel rules** in your workspace (rollout in progress; if you don't see it yet, contact hello@sourceloop.ai for early access). ## How channel attribution propagates Once a visit is classified into a channel, the value persists across: - **Every conversion** that visitor produces, form, meeting, chat, payment, both first-touch and last-touch - **Every report** that groups by channel (Traffic dashboard, Content dashboard, Funnels, Contacts Hub filters) - **Every CRM contact** created from the visitor, written into the channel field of HubSpot / Salesforce / Pipedrive - **Every outgoing webhook** that fires when the lead is captured or updated A channel set on the first session sticks as the **first-touch channel**. A channel set on the converting session is the **last-touch channel**. These are independent and most leads end up with different values for each (e.g., first-touch: Paid Search; last-touch: Direct). ## What's next - **Understand the click IDs that drive paid-channel detection:** [Click IDs explained](/help/click-ids-explained/). - **Tag your campaigns so they classify correctly:** [UTM best practices](/help/utm-best-practices/). - **Diagnose why a specific visit shows up as Direct:** [Why is my source showing as Direct?]() (companion troubleshooting article). - **See the channels in action on the dashboard:** [Traffic dashboard](/help/traffic-dashboard/). ## Frequently Asked Questions ### Why does so much of my paid traffic show as Direct? Three usual reasons. (1) Your campaign URLs aren't tagged with UTM parameters and the click ID was stripped (iOS Safari with strict tracking prevention, browser extensions, or a redirect chain that drops query strings). (2) The visitor came back later through a bookmark / typing your URL after first seeing the ad. (3) The ad platform's auto-tagging is disabled and you haven't added manual UTMs. The fix is always to tag every paid link with UTM parameters in addition to relying on click IDs. ### What's the difference between Paid Search and Organic Search? Paid Search means the visitor clicked a paid ad on a search engine (Google Ads, Microsoft Ads, etc.), detected by a click ID (gclid, msclkid) on the landing URL or by `utm_medium=cpc`. Organic Search means the visitor clicked an unpaid result on the same search engines, detected by the referrer domain and the absence of any paid click ID. Both flow from the same engines but answer different marketing questions. ### I added utm_source=facebook and utm_medium=cpc but it still shows as Direct. Why? The UTMs are read on the landing session only. If the visitor came directly, then visited again later through a tagged link in a new session, the new session captures the UTMs but the first one stays Direct. If you mean a single landing session is still showing Direct, double-check the URL the visitor actually arrived on (some redirect chains drop query strings); open the page with `?sl_debug=1` to see what the tracker recorded. ### Can I create custom channels? Yes, custom channel rules let you override the defaults. For example, you can map `utm_source=newsletter` to a custom channel called "Email Marketing" instead of the default Email bucket, or split Paid Social into separate "Meta Ads" and "LinkedIn Ads" channels. See custom channel rules for the setup. ### Does SourceLoop's channel definition match Google Analytics 4's? Mostly yes, the defaults follow GA4's standard channel grouping (Paid Search, Organic Search, Paid Social, Organic Social, Email, Referral, Direct, Display, Affiliate, Audio, SMS, Push). The differences are (1) SourceLoop adds Paid Shopping and Organic Shopping for ecommerce, (2) SourceLoop uses click IDs as a stronger signal than referrers (a referrer-less click on a gclid still resolves to Paid Search), and (3) SourceLoop's Direct bucket is narrower because click IDs can rescue visits that GA4 would flag as Direct. ### How is the channel decision made, in what order? Click IDs first (highest confidence), then UTM parameters (utm_medium + utm_source), then the referring domain (matched against a curated list), then Direct as the fallback. The first signal that produces a confident classification wins; later signals don't override earlier ones in the same session. ### Why does my Reddit / Twitter / YouTube visit show as Referral instead of Organic Social? Because the referrer crossed into your site without a UTM tag or platform click ID. Without those signals, SourceLoop classifies by referrer domain, and Reddit / Twitter / YouTube referrals land in the broad Organic Social bucket by default. The fix is to tag any links you control (your own social posts, your channel description, your community bio) with `utm_medium=social` so the visit lands in Organic Social explicitly. Untagged shares from other people will continue to land as Organic Social automatically based on the referring domain. --- # 9 Click ID and their Ad Platforms [With Example] Plain-English reference for every paid click ID SourceLoop reads, gclid, gbraid, wbraid, msclkid, fbclid, li_fat_id, ttclid, epik, rdt_cid. Source: https://sourceloop.ai/help/click-ids-explained/ Updated: 2026-05-29 --- A **click ID** is an opaque token that an ad platform appends to your landing URL when someone clicks one of your ads. Different platforms use different names, all of them serve the same purpose: a high-confidence proof that "this visit came from this ad." For attribution accuracy, click IDs do three things UTMs alone can't: 1. **Survive privacy stripping.** Some iOS browsers and ad blockers remove the `Referer` header but leave query strings intact. The click ID hangs on. 2. **Encode platform-specific data the ad network can decode.** Pushing an offline conversion back to Google Ads with the original `gclid` lets Google match the conversion to the exact campaign, ad group, keyword, audience, and creative the click came from, depth no UTM can carry. 3. **Are set automatically.** Auto-tagging is on by default in most ad accounts, so click IDs land on every paid click without per-campaign URL-tagging discipline. This article lists every click ID SourceLoop reads, what platform sets it, and what channel it implies. ## The full table | Click ID | Platform | Channel | Notes | |---|---|---|---| | **`gclid`** | Google Ads | Paid Search | The classic Google Ads click ID. Set on every paid Google Search and Display click when auto-tagging is on. | | **`gbraid`** | Google Ads (iOS) | Paid Search | iOS app campaigns when the user has restricted Apple's App Tracking Transparency (ATT). Functionally equivalent to `gclid` for SourceLoop's purposes. | | **`wbraid`** | Google Ads (iOS web) | Paid Search | iOS web clicks under ATT restrictions. Functionally equivalent to `gclid`. | | **`msclkid`** | Microsoft Ads (Bing) | Paid Search | The Microsoft equivalent of `gclid`. Set on every Microsoft Ads click when Final URL Auto-tagging is on. | | **`fbclid`** | Meta (Facebook + Instagram) | Paid Social | Set on every click from inside Meta's ecosystem, paid ads, organic posts, profile-bio links, Messenger shares. The presence of `fbclid` doesn't strictly mean paid; it means the click originated from Meta. | | **`li_fat_id`** | LinkedIn | Paid Social | LinkedIn's First-Party Ads Tracking UUID. Set by the LinkedIn Insight Tag on the first paid-ad landing for a visitor; persists across sessions. | | **`ttclid`** | TikTok | Paid Social | TikTok Ads click identifier. | | **`epik`** | Pinterest | Paid Social | Pinterest Promoted Pin click identifier. | | **`rdt_cid`** | Reddit | Paid Social | Reddit Ads click identifier. | ## How SourceLoop reads them The tracker reads every click ID present on the landing URL and stores them on the visitor's session and (on conversion) on the lead. From there: - **For channel classification**, the click ID is the highest-confidence signal. A `gclid` on the URL means Paid Search even if the referrer is missing and no UTMs are set. - **For offline conversion sync**, when you push a lead back to Google Ads / Microsoft Ads / Meta / LinkedIn, SourceLoop sends the stored click ID alongside the conversion so the platform can match it to the original click record. - **For lead detail and CSV export**, all stored click IDs appear as columns on the lead row. ## Why both UTM and click ID UTMs and click IDs are complementary, not redundant. Each survives where the other fails: | Scenario | UTM survives? | Click ID survives? | |---|---|---| | iOS Safari with strict tracking prevention (referrer stripped) | ✅ Yes | ✅ Yes | | URL shortener that strips query strings | ❌ No | ❌ No | | Redirect chain that drops your custom params but keeps platform-set ones | ❌ Often no | ✅ Yes | | Bookmarked URL revisited later (no original click) | ❌ No | ❌ No | | Email forwarded with link intact | ✅ Yes | ✅ Yes | | Ad platform auto-tagging is disabled | ✅ Yes (if you set manual UTMs) | ❌ No | | You forgot to add UTMs to a paid campaign | ❌ No | ✅ Yes (auto-tagging) | The best practice is to run both: keep auto-tagging on in every ad account AND add manual UTMs (`utm_source`, `utm_medium`, `utm_campaign`) to every paid link. ## Where click IDs power offline conversions When you connect Google Ads, Microsoft Ads, Meta, or LinkedIn to SourceLoop and turn on offline-conversion sync, the lead's stored click ID is what makes the round trip possible. The flow: 1. Visitor clicks a Google Ad. `gclid=ABC...` lands on the URL. SourceLoop stores it. 2. Visitor fills out a form. SourceLoop creates a lead with the `gclid` attached. 3. Sales qualifies the lead in your CRM. 4. SourceLoop's sync to Google Ads fires an offline conversion event with the `gclid` and the lead value. 5. Google Ads receives the event, looks up the `gclid` to find the original click, and credits the conversion (with full revenue) to the right campaign, ad group, and keyword. Without the click ID, the offline conversion still uploads (matched by hashed email instead), but Google's match rate drops from ~99% to roughly 60-80%, and you lose campaign-level granularity. See the per-platform guides: - [Configure Google Ads offline conversions](/help/configure-google-ads-offline-conversions/) - [Configure Microsoft Ads offline conversions](/help/configure-microsoft-ads-offline-conversions/) - [Configure Meta Conversions API](/help/configure-meta-conversions-api/) - [Configure LinkedIn offline conversions](/help/configure-linkedin-offline-conversions/) - [Configure TikTok Events API](/help/configure-tiktok-events-api/) ## How to verify click IDs are landing The fastest way to check that auto-tagging is on and SourceLoop is picking up the click IDs: 1. Click one of your own live ads (preferably from an incognito window so the click isn't deduped against your existing session). 2. After landing, look at the URL bar. You should see the click ID as a query parameter. 3. Open `?sl_debug=1` on the landing page and check the browser console; SourceLoop logs every captured click ID under the `[sourceloop/v3]` prefix. 4. Convert (submit a form or book a meeting) on the same session. 5. Open the lead in the SourceLoop Contacts Hub. The click ID is listed in the lead detail drawer under the technical/attribution panel. If you can see the click ID on the URL but not on the lead, the most common cause is the tracker not loading on the landing page; double-check the tracking pixel install. ## What's next - **How click IDs feed into the channel decision:** [How SourceLoop classifies marketing channels](/help/channel-definitions/). - **Set manual UTMs alongside click IDs:** [UTM best practices](/help/utm-best-practices/). - **Push the click IDs back to the ad platform for offline conversions:** [Configure Google Ads offline conversions](/help/configure-google-ads-offline-conversions/), [Configure Meta Conversions API](/help/configure-meta-conversions-api/), [Configure LinkedIn offline conversions](/help/configure-linkedin-offline-conversions/). ## Frequently Asked Questions ### Do I need click IDs if I'm already using UTM parameters? Both, ideally. Click IDs are auto-appended by the ad platform's auto-tagging and survive when the visitor lands on a page that ignores or strips your UTMs (a redirect chain, a cached old page, an embed without query-string passthrough). UTMs are explicit, controlled by you, and survive when the click ID is stripped (some iOS browsers, some ad blockers). Running both is belt-and-suspenders and produces the cleanest attribution. ### How do I check if my ad platform is auto-tagging click IDs? Google Ads, Account Settings -> Auto-tagging -> ensure "Tag the URL that people click through from my ad" is on. Microsoft Ads, Accounts & Billing -> Auto-tagging -> Final URL Auto-tagging. Meta, Account Settings -> Advanced -> URL parameters -> ensure fbclid is appended (it usually is by default). Click a live ad in private/incognito and inspect the URL you land on; if it has the click ID in the query string, auto-tagging is on. ### What does it mean if a lead has a gclid but no UTMs? It means Google Ads auto-tagging is on and you haven't added manual UTMs to your campaigns. SourceLoop can still attribute the lead to Paid Search via the gclid, but you lose the granular campaign / ad-group / keyword detail that UTMs would carry. For full attribution depth, set up manual UTMs (utm_source=google, utm_medium=cpc, utm_campaign={campaign_name}) in addition to auto-tagging. ### What's the difference between gclid, gbraid, and wbraid? All three are Google Ads click IDs. gclid is the classic identifier, set on most desktop and Android web clicks. gbraid replaces gclid for iOS app campaigns when the user has opted out of ATT. wbraid replaces gclid for iOS web clicks where ATT consent is restricted. Functionally for SourceLoop attribution, any of the three signals "Paid Search" with high confidence. For pushing offline conversions back to Google Ads, the same field accepts all three, see the Google Ads offline conversion guide. ### Why does fbclid show on direct-typed URLs sometimes? Because Meta's tracking embeds fbclid in any link the user clicked from inside Facebook or Instagram, including organic posts, profile bios, and shared messages in Messenger, not only paid ads. SourceLoop treats a URL with fbclid + no utm_medium as Paid Social by default, which usually matches intent (the click came from inside Meta's ecosystem), but you can override this with a custom channel rule if you specifically want organic Facebook clicks bucketed as Organic Social. ### Are click IDs personally identifiable information (PII)? No. A click ID is an opaque identifier the ad platform generated, it doesn't contain the user's name, email, location, or any directly identifying field. It can be linked back to a click record on the ad platform's side, which is how offline-conversion uploads work, but on its own it's not PII under GDPR / CCPA. ### How long does a click ID stay valid? Click IDs are valid for the lifetime of the ad platform's attribution window, typically 30 days for Google Ads, 7 days for Meta (default click-attribution window). SourceLoop stores the click ID on the lead indefinitely, but pushing it back to the ad platform for offline-conversion attribution only works within the window. After it expires, the click ID still exists on the lead but the ad platform won't accept it for new offline-conversion uploads. --- # UTM best practices for clean marketing attribution Tag campaigns with UTM parameters that produce clean attribution. The five standard parameters, naming conventions, common mistakes, and templates. Source: https://sourceloop.ai/help/utm-best-practices/ Updated: 2026-05-29 --- **UTM parameters** are the five query-string fields you can add to any URL so SourceLoop (and Google Analytics, HubSpot, and every other attribution tool) knows where a visitor came from. They look like this in a URL: ``` https://yoursite.com/pricing?utm_source=newsletter&utm_medium=email&utm_campaign=jan-launch ``` When the visitor lands on your site with a UTM-tagged URL, the tracker reads the parameters and stores them on the visitor's session and on every conversion that follows. This article is the practical guide: which parameters matter, naming conventions that scale, and the mistakes that cause the most cleanup pain six months in. ## The five UTM parameters | Parameter | Required? | What it means | Example | |---|---|---|---| | **`utm_source`** | Yes | The platform or publication that sent the traffic | `google`, `linkedin`, `newsletter`, `partner-name` | | **`utm_medium`** | Yes | The marketing channel | `cpc`, `email`, `social`, `affiliate`, `referral` | | **`utm_campaign`** | Strongly recommended | The specific campaign name | `q1-launch`, `pricing-relaunch-2026`, `mql-nurture` | | **`utm_content`** | Recommended for paid | Differentiates ads in the same campaign | `headline-a`, `image-2`, `cta-blue` | | **`utm_term`** | Optional, paid search only | The search keyword (auto-filled by Google Ads when ValueTrack is on) | `{keyword}`, `marketing+attribution+software` | Two are mandatory for channel attribution to work: `utm_source` and `utm_medium`. Without them, the URL falls back to referrer or click ID classification, which is less reliable. ## Naming conventions that scale The hardest part of UTMs isn't adding them; it's keeping the values consistent across a team and over years. Three rules cover 90% of the pain: ### Rule 1: Lowercase only UTM values are case-sensitive in reports. `utm_source=Facebook` and `utm_source=facebook` will show as two separate sources. Establish a lowercase-only convention on day one. ### Rule 2: Use dashes, not spaces or underscores Spaces get encoded as `%20` and break the eye-readability of URLs in dashboards. Underscores are fine technically but visually crowded. Dashes (kebab-case) sort alphabetically, read cleanly in the URL bar, and copy/paste reliably: ``` ✅ utm_campaign=q1-launch-2026 ❌ utm_campaign=Q1 Launch 2026 ❌ utm_campaign=Q1_Launch_2026 ``` ### Rule 3: Establish a `utm_campaign` template Without a template, every marketer types `utm_campaign=` differently and your dashboards fragment. A simple template that scales: ``` {objective}-{audience}-{date} ``` Examples: - `demo-smb-2026q2` - `signup-enterprise-jan2026` - `pricing-test-2026q1` The objective is what you want the visitor to do (demo, signup, trial, content). The audience is who you targeted (smb, enterprise, freelancers). The date lets you compare campaigns across periods without name collisions. For paid ads, add a fourth segment for variant identification (so `utm_content` is reserved for ad-level variation): ``` {objective}-{audience}-{date}-{variant} e.g., demo-smb-2026q2-googlecpc ``` ## Recommended UTMs per channel ### Google Ads / Microsoft Ads ``` utm_source=google (or `bing` for Microsoft) utm_medium=cpc utm_campaign={campaign_name} ← use ValueTrack to auto-fill utm_content={creative} utm_term={keyword} ← ValueTrack auto-fill ``` ValueTrack parameters: `{campaign}`, `{adgroup}`, `{keyword}`, `{matchtype}`, `{creative}` are Google's auto-fill placeholders that Google replaces with the actual values when an ad is served. Set them once at the campaign level (Google Ads → Campaign settings → Tracking template) and every ad gets the right UTMs automatically. See Google's [ValueTrack reference](https://support.google.com/google-ads/answer/2375447). Even with auto-tagging click IDs on, manual UTMs are still recommended for the campaign-level reporting they unlock. ### Meta Ads (Facebook + Instagram) ``` utm_source=facebook (or `instagram`) utm_medium=paidsocial utm_campaign={campaign-name} utm_content={ad-creative-name} ``` Meta has its own dynamic parameters (`{{campaign.name}}`, `{{ad.id}}`, `{{adset.name}}`) you can set at the ad-set level. See Meta's [URL parameters reference](https://www.facebook.com/business/help/2360940870872492). ### LinkedIn Ads ``` utm_source=linkedin utm_medium=paidsocial utm_campaign={campaign-name} utm_content={creative-id} ``` LinkedIn doesn't have ValueTrack-style auto-fill at the same depth, so this is mostly manual setup per campaign. ### Email (newsletters, broadcasts, transactional) ``` utm_source=newsletter (or your-app's name) utm_medium=email utm_campaign={send-name} e.g., 2026-04-newsletter ``` Most email tools (Mailchimp, ConvertKit, Klaviyo, Loops, Postmark) support per-link UTM templates or per-send UTM macros. Set the template once at the brand level so every email send produces consistent UTMs without remembering to tag. ### Organic social (your own posts) ``` utm_source=twitter (or linkedin, facebook, instagram, threads) utm_medium=social utm_campaign=organic-{topic} e.g., organic-launch-week ``` Worth tagging your own posts because referral classification by domain lumps all social platform clicks into Organic Social. Manual `utm_campaign` on your own posts lets you see exactly which posts drove conversions. ### Partner / affiliate / sponsored placements ``` utm_source=partner-name (use a slug, e.g., g2, captera, productscope) utm_medium=partner (or affiliate, sponsorship) utm_campaign={placement} e.g., g2-roundup-2026 ``` ### Internal cross-links (do NOT use UTMs) UTMs on internal links restart the visitor's session and overwrite the original attribution. Bad: ``` ❌ [See pricing](/pricing?utm_source=blog&utm_medium=internal) ``` Use plain links for internal navigation. Only tag links that take the visitor away from your site and back. ## Common mistakes | Mistake | What goes wrong | |---|---| | **Mixed case** | `Google` and `google` show as two sources, splitting the same campaign in reports | | **Spaces in values** | URL encodes to `%20`, looks ugly in dashboards and breaks some sorting | | **UTMs on internal links** | Restarts the visitor's session, overwriting the original first-touch attribution | | **No `utm_medium`** | Visit falls back to referrer / click ID classification, often lands as "Direct" | | **Free-form `utm_campaign`** | "Spring promo" vs "spring_promo" vs "Spring Campaign 2026" all show as different campaigns | | **Reusing the same `utm_campaign` across years** | Year-over-year reports can't separate the 2025 launch from the 2026 launch | | **Putting the email subject in `utm_campaign`** | Subjects get long; URL becomes ugly; comparison across emails is hard | | **Tagging organic search URLs** | Pointless; search engines manage their own click parameters. Tagging cannibalises the Organic Search bucket | ## Tooling that helps You don't need fancy tools, but a few make UTM discipline easier: | Tool | What it does | |---|---| | **A UTM builder** | Anything that enforces your naming convention. Google's [Campaign URL Builder](https://ga-dev-tools.google/campaign-url-builder/), [utm.io](https://web.utm.io), or a Google Sheet with formulas | | **A shared UTM tracker (spreadsheet)** | Single source of truth for "what UTMs did we use for the Q1 demo campaign?". One row per campaign with the exact UTM string | | **Ad platform campaign-level templates** | Google Ads tracking templates, Meta dynamic parameters, LinkedIn campaign URL macros. Each lets you set UTMs once per campaign and the platform auto-fills every ad | | **Email tool default UTM template** | Mailchimp, Loops, Postmark all support per-link UTM templates. Set them once at the brand level | ## What's next - **How SourceLoop turns UTMs into channels:** [How SourceLoop classifies marketing channels](/help/channel-definitions/). - **The other half of the attribution signal:** [Click IDs explained](/help/click-ids-explained/). - **See the UTM breakdown on every dashboard:** [Traffic dashboard](/help/traffic-dashboard/). - **Pick the right attribution model now that your data is clean:** [7 Types of Attribution Models](/help/types-of-attribution-models/). ## Frequently Asked Questions ### Do I really need UTMs if my ad platforms auto-tag click IDs? Yes, for two reasons. (1) Click IDs flag the channel (Paid Search, Paid Social) but don't carry campaign / ad-group / keyword / creative detail in a form SourceLoop can decode without calling back to the ad platform. UTMs do, instantly and offline. (2) Click IDs only exist for paid channels, your newsletter, blog cross-links, partner placements, and Slack-shared links all need UTMs because no platform sets a click ID for them. ### Should I use uppercase or lowercase in UTM values? Lowercase, always. UTMs are case-sensitive in reports, so utm_source=Google and utm_source=google show as two separate sources. Establishing a "everything lowercase" rule from day one prevents weeks of cleanup later. ### What's a clean naming convention for utm_campaign? Something machine-parseable. A common pattern is {objective}-{audience}-{date}, e.g., demo-smb-2026-q2 or signup-enterprise-jan2026. The dashes (not spaces) let you sort campaigns alphabetically and filter cleanly. Avoid free-form descriptions like "spring campaign for SMB" which make analytics filtering painful. ### How many UTM parameters do I need? Two are required for channel attribution to work (utm_source, utm_medium). Three is the minimum for useful reporting (add utm_campaign). Four is best for paid (add utm_content to differentiate creatives). Five if you care about keyword-level reporting (add utm_term for paid search). ### My team keeps forgetting to add UTMs. What's the easiest fix? Three habits. (1) Use a UTM builder, every link you create goes through it; the builder enforces lowercase and a naming convention. (2) Bookmark a Google Sheet of approved UTMs per campaign so the team copies from it. (3) For ad platforms, set the UTM template at the campaign level (Google Ads -> Campaign settings -> Tracking template) so every ad's URL is appended automatically. With a campaign-level template, you stop relying on humans to remember. ### Will UTMs survive iOS Safari's privacy features? Yes. iOS Safari (and ITP-style privacy modes generally) strip the Referer header but leave query strings intact. UTM parameters survive intact. The same goes for most ad blockers and privacy extensions. UTMs are the most resilient attribution signal in modern browsers. --- # How SourceLoop stitches identities and deduplicates contacts How SourceLoop merges sessions, devices, and form submissions into one contact. The signals used, when merges happen, and handling duplicates. Source: https://sourceloop.ai/help/identity-stitching-and-dedup/ Updated: 2026-05-29 --- When a visitor lands on your site multiple times, from multiple devices, through multiple campaigns, and converts via multiple channels, SourceLoop has to decide: is this one person or several? The way it decides is **identity stitching**, and the by-product is contact **deduplication**, one row in the Contacts Hub per real human, not one row per session or form submission. This article covers how stitching works, when merges happen, and what to do about the duplicates that occasionally slip through. ## The model SourceLoop maintains an **identity graph** per website. Each contact is one node. Every node can have: - **One or more anonymous identifiers** — the cookie-based IDs the tracker assigns when a new browser lands on the site. One per device/browser usually. - **At most one canonical email** — the primary contact email used for deduplication. - **At most one canonical phone** — secondary identifier for phone-only conversions. - **Optional external IDs** — your internal user ID, your CRM contact ID, your Stripe customer ID, etc., used to stitch across systems. Two browser sessions that share **any** of those identifiers point to the same contact. Two sessions that share **none** are treated as two contacts. ## How merging happens A merge happens whenever a session emits an identifier (email, phone, or external ID) that's already attached to an existing contact in your workspace. ### Example: same person, two devices 1. Tuesday: Jane browses your pricing page from her laptop (Chrome). The tracker creates **anonymous_id_A** for her browser. Jane is anonymous, one row in your Visitors view. 2. Wednesday: Jane reads your blog from her phone (Safari). The tracker creates **anonymous_id_B** for that browser. Two anonymous visitors in your Visitors view now (laptop and phone, not yet linked). 3. Thursday: Jane submits a form from her phone with `jane@example.com`. SourceLoop creates a contact with `anonymous_id_B` linked. One row in Leads, one row left in Visitors (the laptop, still anonymous). 4. Friday: Jane visits your pricing page from her laptop and logs into her account. Your app calls `window.sourceloop.identify({email: "jane@example.com"})`. SourceLoop sees `jane@example.com` already exists as a contact, and merges `anonymous_id_A` into that contact's anonymous ID list. The laptop visitor disappears from Visitors. The contact in Leads now shows BOTH the laptop journey and the phone journey, every page view from both, every session from both, in chronological order. ### Example: two different emails, one person 1. Jane submits a contact form with her work email `jane@acme.com`. Contact created. 2. Two months later, Jane signs up for the newsletter with her personal email `jane.smith@gmail.com`. Different email, different contact created. These stay separate by default, because from SourceLoop's view, they're two distinct identifiers. If you want to merge them, mark one as a duplicate of the other in the lead detail drawer (see below) or have your CRM sync the merge so the next inbound sync from your CRM aligns the records. ## How dedup happens within a single conversion path Beyond cross-session merging, SourceLoop dedups single-conversion paths so a fast double-submit, a quick reload-and-resubmit, or a webhook retry doesn't create two rows. | Path | Dedup signal | Window | |---|---|---| | **Form submission** (auto-captured by tracker) | Email address | A few minutes | | **Incoming webhook delivery** | The `chat_id` field if you pass one, otherwise email | Workspace-wide, no time limit | | **Stripe / Lemon Squeezy / Paddle / Polar / Dodo Payments** | Provider's event id | Workspace-wide | | **CRM inbound sync** | The CRM's contact id | Workspace-wide | | **Manual contact creation** | None, you can intentionally create two contacts with the same email; the system warns but allows it | If the same email submits the same form three times in 10 seconds (the visitor clicked submit too many times), SourceLoop produces one contact with one conversion event, not three. ## The Mark as duplicate flag When two real contacts represent the same human (different email addresses, accidental double-create by two team members, a CRM merge that didn't propagate), the cleanest fix is the **Is duplicate** flag in the lead detail drawer: 1. Open the duplicate contact (the one you want to mark, not the canonical one). 2. In the **Additional Information** section, toggle **Mark as duplicate**. 3. Save. What changes: - The contact stays in the database with all its attribution intact (no data loss). - The default Contacts Hub view hides duplicates; the **Show duplicates** filter shows them back. - Outgoing webhooks for that contact include `is_duplicate: true` so downstream systems can filter accordingly. - CRM sync respects the flag, the canonical contact gets the new updates and the duplicate is skipped. If you'd rather merge two contacts entirely (combine their journeys, conversions, revenue), that's a more involved operation. For now, contact hello@sourceloop.ai with the two contact IDs and we'll do the merge manually. ## Anonymous IDs in practice The anonymous ID is a long random string the tracker assigns the first time a visitor lands on your site, stored in the visitor's browser cookies / local storage. It persists across: - Multiple page views on the same site - Multiple sessions (the visitor returning days or weeks later) - Browser restarts It does NOT persist across: - Different browsers on the same device (Chrome and Safari are two visitors) - Different devices (laptop and phone are two visitors until they identify) - Cleared cookies / incognito → normal browsing (a fresh anonymous ID gets assigned) Where you see the anonymous ID: - In the tracker's debug output (`?sl_debug=1` on any page) - As the `sourceloop_id` field on every payment integration's `checkoutMetadata()` call - In the `anonymous_id` field on outgoing webhook payloads - In the visitor's URL query string when crossing into a cross-domain destination (the linker appends it as `_sl=...`) ## Cross-domain stitching If your visitor moves from `yoursite.com` to `checkout.yoursite.com` (subdomain stays within the same eTLD+1), the tracker handles stitching automatically; the cookies are shared. If they move to a different domain (`yoursite-checkout.com`), you need to list that domain in `window.SourceLoopConfig.crossDomains` and have a tracker on the destination site too. The tracker then appends the anonymous ID to outbound URLs as a `_sl=...` parameter, and the destination tracker reads it on landing and continues the same visitor's session. See [Cross-domain and cross-subdomain tracking](/help/cross-domain-and-subdomain-tracking/) for the config example. ## Common pitfalls **Calling `identify()` with the wrong email.** If a user has email A and your code accidentally calls `identify({email: "...wrong-email..."})`, SourceLoop creates a second contact for the wrong email or attaches the wrong identifier to the right contact. Always identify with the email the user actually owns. **Clearing cookies during testing.** When QA testing, repeatedly clearing cookies creates a new anonymous ID each time, which makes the Contacts Hub look like it's accumulating dozens of duplicates. Use incognito + a fresh email per test to isolate test data, then delete the test contacts when done. **Hoping the tracker fingerprints across devices without an identifier.** It doesn't, and it can't without invasive techniques no one should be running in 2026. The only way to bridge devices is for the visitor to identify (log in, submit a form, complete a purchase) on each device. **Treating duplicate emails in the CRM as "SourceLoop's bug".** If your CRM has two contacts for `jane@example.com` and SourceLoop syncs both, you'll see two contacts on the SourceLoop side too. Fix it in the CRM first, then let the next sync align. ## What's next - **See the contact view that this article describes:** [How to use the SourceLoop Contacts (Leads) table](/help/contacts-leads-table/). - **Understand how sessions work alongside identities:** [How SourceLoop defines a session](/help/session-definition/). - **Learn how the JavaScript SDK exposes identify and identifiers:** [Install the SourceLoop SDK](/help/install-the-sourceloop-sdk/). ## Frequently Asked Questions ### I see duplicate leads in my Contacts Hub. What's happening? Three usual causes. (1) The visitor submitted the form twice quickly enough that both submissions made it through before dedup could match them. (2) The visitor used two different emails (a personal and a work email) and SourceLoop treated them as two different people because that's accurate. (3) Two team members manually created a contact at the same time. For (1), SourceLoop's dedup window catches most rapid duplicates automatically. For (2) and (3), use the Mark as duplicate toggle on the lead detail drawer to keep both records linked but mark one as the canonical version. ### When does SourceLoop merge two browser sessions into one contact? As soon as the same email or phone is captured on both sessions. If session A on a laptop submits the form with jane@example.com, and session B on a phone the next day also identifies as jane@example.com, SourceLoop merges both sessions' anonymous identifiers into one contact named Jane. From then on, every new session under either anonymous identifier (laptop OR phone) updates the same contact. ### Can SourceLoop merge across devices without an email? No. Without a shared identifier (email or phone), two browser sessions on two different devices are treated as two different anonymous visitors. They merge the first time the same identity signal appears on both. This is the same behaviour as every modern attribution tool, cross-device identity without shared signals is impossible without invasive fingerprinting. ### What's an anonymous ID and where do I see it? An anonymous ID is the cookie-based identifier SourceLoop sets on the visitor's browser the first time they land on your site. It's stored client-side, persists across sessions on the same browser, and powers the pre-conversion journey timeline. You can see it in the SourceLoop tracker's debug output (load any page with `?sl_debug=1`) or as the sourceloop_id field passed to payment integrations. ### How does this affect attribution? When two sessions merge into one contact, their journeys merge too. The contact's first-touch attribution is the earliest session in the merged set; the last-touch attribution is whichever session contained the conversion event. The merged journey is what you see on the lead detail drawer's timeline, every session, every page view, every event, in chronological order. ### How does dedup work for incoming webhook deliveries? Two ways. (1) If you pass a chat_id (or similar unique identifier) in the JSON payload, SourceLoop dedups by that field within the workspace. (2) Otherwise, SourceLoop dedups by email within a short window (so a fast double-fire of the same form doesn't create two contacts). The first delivery wins; later deliveries with the same identifier update the existing contact instead of creating a new one. ### Does the Mark as duplicate flag delete the duplicate? No. It leaves the record in place but hides it from your default Contacts Hub view (you can re-show it with the filter). The original lead remains intact with all its attribution. The is_duplicate flag is also passed through to outgoing webhooks and CRM syncs so downstream systems can filter accordingly. --- # How SourceLoop defines a session When SourceLoop starts a session, when it ends one, how that shapes per-visit metrics on your dashboards, and where it differs from Google Analytics 4. Source: https://sourceloop.ai/help/session-definition/ Updated: 2026-05-29 --- A **session** in SourceLoop is one continuous visit, a single bounded period during which a visitor interacts with your site. Every metric on the Traffic dashboard expressed "per session" (session count, pages per session, session duration, bounce rate) depends on this definition. Per-channel and per-campaign attribution at the session level is what powers the marketing-source story. This article covers exactly when SourceLoop starts a new session, when it ends one, and how those decisions feed into your dashboards. ## What starts a new session Four triggers can start a new session for an existing visitor: | Trigger | Example | |---|---| | **First page view ever** | A new visitor lands on your site for the first time. | | **30+ minutes of inactivity** | The visitor was browsing, walked away from their computer, came back 45 minutes later. New session. | | **Attribution change** | The visitor arrives with a new `utm_source` / `utm_medium` / `utm_campaign`, a new paid click ID, or a referring domain that maps to a different channel. New session. | | **Midnight (workspace timezone)** | A visitor browsing at 11:50 PM keeps browsing until 12:10 AM. Two sessions, one ending at midnight, one starting. | These triggers are checked on every page view and every custom event. ## What does NOT start a new session | Non-trigger | Why it doesn't fire a new session | |---|---| | **Tab switch** | The 30-minute timer tracks tracker activity (page views, custom events, scroll pings), not tab visibility. A visitor with your tab open in the background isn't "active" but isn't "inactive" either; the timer doesn't tick unless they switch back and resume. | | **Browser refresh** | Same anonymous ID, same session, same attribution. The refresh just emits another page-view event in the same session. | | **Internal navigation** | Clicking from `/pricing` to `/docs` to `/contact` is one session, regardless of how many page views. | | **Cross-subdomain navigation** | `yoursite.com` → `app.yoursite.com` is the same session (cookies are shared on the same eTLD+1). | | **Cross-domain navigation with the linker** | `yoursite.com` → `yoursite-checkout.com` with the cross-domain linker configured is the same session (the anonymous ID is passed via URL). | ## How attribution attaches to a session Each session has exactly one set of attribution fields: | Field | Set from | |---|---| | Channel | The classification rules (see [Channel definitions](/help/channel-definitions/)) | | Source | `utm_source` parameter, or platform inferred from click ID, or referring domain | | Medium | `utm_medium` parameter, or `cpc/cpm/organic` inferred from click ID, or `organic`/`referral` inferred from referrer | | Campaign | `utm_campaign` parameter | | Content | `utm_content` parameter | | Term | `utm_term` parameter | | Landing page | The URL of the first page view in this session | | Referrer | `document.referrer` at session start | | Click IDs | `gclid`, `fbclid`, `msclkid`, etc., from the URL on session start | Once a session starts, these values stay frozen for its duration. The visitor navigating internally on your site doesn't change them. This is why the attribution-change trigger above is so important, it ensures each session has exactly one truthful set of attribution. ## Sessions roll up into the visitor and the contact ``` 1 contact (1 real person) → N anonymous IDs (1 per device/browser they've used) → N sessions per anonymous ID (1 per visit) → N page views per session (1 per page they viewed) → N events per session (custom events, scroll pings, conversion events) ``` The Traffic dashboard's "Sessions" metric counts the sessions inside the date range. The "Visitors" metric counts unique anonymous IDs. The "Contacts" metric counts identified contacts (with an email or phone). A single high-engagement contact who browsed your site five separate times across two weeks contributes: - 1 contact - 1 anonymous ID (assuming same browser each time) - 5 sessions - 25-or-so page views (assuming 5 pages per session) ## Session-level vs visitor-level metrics Most dashboards offer both: | Metric | Session-level | Visitor-level | |---|---|---| | **Count** | Number of sessions | Number of unique anonymous IDs | | **Conversion rate** | Conversions ÷ sessions | Conversions ÷ visitors | | **Bounce rate** | Sessions with 1 page view ÷ all sessions | Visitors who only bounced ÷ all visitors | | **Pages per session** | Total page views ÷ session count | Total page views ÷ visitor count | | **Duration** | Time from first to last event in a session | Sum of all sessions per visitor | For attribution work, session-level metrics are usually more meaningful. Visitor-level metrics make sense for "how engaged is my audience over time?" but session-level metrics answer "did this specific campaign produce engaged visits?". ## How session resets affect attribution The attribution-change trigger is the one with the biggest implication. Consider: 1. **9:00 AM** — Jane arrives via a Google Ads click. Session 1 begins with first-touch channel = Paid Search. 2. **10:00 AM** — Jane leaves your site without converting. 3. **10:25 AM** — Jane sees a LinkedIn ad in her feed and clicks it. She returns to your site. **30 minutes haven't elapsed, but the click ID and referrer have changed, so SourceLoop starts a new session.** Session 2 begins with channel = Paid Social. 4. **10:45 AM** — Jane submits the demo form during Session 2. The conversion is attributed: - **First-touch** = Paid Search (Session 1's channel — the earliest session in Jane's identity graph) - **Last-touch** = Paid Social (Session 2's channel — the converting session) Both touches are visible on the lead, and you can group revenue / leads by either model on the [Attribution dashboard](/help/types-of-attribution-models/). If SourceLoop hadn't reset the session on the attribution change, Session 1 would still be active when the form fired, and the conversion would attribute entirely to Paid Search, hiding the role LinkedIn played in closing the deal. ## What's next - **Understand how channels are picked on each session:** [How SourceLoop classifies marketing channels](/help/channel-definitions/). - **See sessions per visitor on the Contacts Hub:** [How to use the SourceLoop Contacts (Leads) table](/help/contacts-leads-table/). - **See session-level metrics on the dashboards:** [Traffic dashboard](/help/traffic-dashboard/). - **Pick the attribution model that scopes how sessions roll up:** [7 Types of Attribution Models](/help/types-of-attribution-models/). ## Frequently Asked Questions ### When does SourceLoop start a new session? Four triggers. (1) The visitor's first page view, ever. (2) The visitor returns after more than 30 minutes of inactivity. (3) The visitor arrives with a different attribution signal than their last session, a fresh utm_source / utm_medium / click ID, or a new referring domain that resolves to a different channel. (4) Midnight in the workspace's timezone (sessions don't span calendar days for cleaner daily reporting). ### Why don't sessions span midnight? Daily reports get muddled when one session straddles two days. By starting a new session at midnight, the visitor's sessions-per-day count stays accurate and the Traffic dashboard's day-over-day comparisons are clean. The trade-off is a visitor browsing from 11:50 PM to 12:10 AM appears as two sessions, but the impact is small (most visitors don't browse over midnight). ### How is this different from Google Analytics 4? GA4 also uses a 30-minute idle timeout but doesn't reset on attribution change or midnight by default. So a GA4 session can span a UTM change and span midnight, which makes campaign-level attribution and day-over-day reports harder to read. SourceLoop's stricter session boundaries mean per-session metrics are more meaningful for attribution work. ### Does a session reset if the visitor clicks a different ad? Yes. If a visitor arrives via Paid Search, leaves, comes back via Paid Social later that hour, SourceLoop starts a new session for the Paid Social arrival. This is so each session has exactly one channel / source / campaign attribution, which makes the Traffic dashboard's per-session counts directly meaningful. ### How long is the typical session? Most B2B sites see median session durations of 1 to 3 minutes and median pageviews per session of 2 to 4. Highly engaged visitors (research-mode prospects browsing pricing + docs + case studies) routinely produce sessions of 10 to 20 minutes. The Traffic dashboard's session-duration metric tracks the median across all sessions in the date range. ### Does a tab switch end a session? No. The 30-minute idle timer is based on tracker activity (pageviews, custom events, scroll-depth pings), not tab visibility. A visitor with your site open in a tab who switches to email and comes back 20 minutes later is still in the same session. The timer only resets to zero when the tracker sees activity. --- # 7 Types of Attribution Models That Marketers Should Know A visual guide to the 7 attribution models every marketer should know, last touch, first touch, last/first non-direct, linear, U-shaped, and time decay. Source: https://sourceloop.ai/help/types-of-attribution-models/ Updated: 2026-05-28 --- If you've ever tried to explain marketing performance in a meeting that includes a paid-acquisition lead AND a content marketer, you've felt the attribution-model problem. Paid wants credit for any conversion their ads touched. Content wants credit for the blog post that started the journey. Both are partially right. **The attribution model is the rule that decides how partial.** This guide walks through the seven models SourceLoop supports, applies the same journey to all seven so you can see the difference at a glance, and tells you which to pick for which decision. ## The example journey we'll use To make the seven models concrete, we'll use the same customer journey throughout this article: | Day | Action | Channel | Touch # | |---|---|---|---| | Day 1 | Saw Meta ad and clicked through | **Meta** (paid social) | 1 | | Day 4 | Searched Google for your brand | **Google** (organic search) | 2 | | Day 7 | Clicked link in newsletter | **Email** | 3 | | Day 14 | Converted to paid plan | — | — | Three marketing touches over a week, one conversion on Day 14. Every model below will assign credit to those three touches differently. ## Quick reference: all 7 models on the example journey | Model | Family | Meta (T1) | Google (T2) | Email (T3) | Best for | |---|---|---|---|---|---| | **1. Last Touch** | Single-touch | 0% | 0% | **100%** | Paid bidding, short cycles | | **2. First Touch** | Single-touch | **100%** | 0% | 0% | Brand and top-of-funnel reporting | | **3. Last Non-Direct** | Single-touch | 0% | 0% | **100%** | Daily reporting, GA-compatible | | **4. First Non-Direct** | Single-touch | **100%** | 0% | 0% | Top-of-funnel for established brands | | **5. Linear** | Multi-touch | 33% | 33% | 33% | Cross-channel assist credit | | **6. Position-Based (U-Shaped)** | Multi-touch | **40%** | 20% | **40%** | Balanced reporting, B2B funnels | | **7. Time Decay** | Multi-touch | ~13% | ~30% | **~57%** | Long sales cycles, considered purchases | The seven models split into two families: **single-touch** (one channel gets 100%) and **multi-touch** (credit is split across the journey). Each is explained in detail below. ## 1. Last Touch Attribution **The rule:** 100% of the credit goes to the **last marketing touch** before the conversion. **Applied to our example:** ``` Meta ad (Day 1, T1) ░░░░░░░░░░░░░░░░░░░░ 0% Google (Day 4, T2) ░░░░░░░░░░░░░░░░░░░░ 0% Newsletter (Day 7, T3) ████████████████████ 100% ↑ all credit here ``` ### When to use it - **Paid acquisition bidding** — Google Ads and Meta's bidding algorithms expect last-click signals. Sending Last Touch conversions back is what makes their auctions train correctly. - **High-intent, short-cycle conversions** — for impulse purchases or single-session conversions, the last touch usually IS the touch that mattered. - **Quick efficiency comparisons** — "which channel produced the most last-touch conversions this week?" is a fast, defensible answer. ### When NOT to use it - **B2B or SaaS with long sales cycles** — Last Touch credits the final email or Google search, ignoring all the brand-building that made the prospect ready to convert. - **When debating whether to keep a top-of-funnel channel** — Last Touch will always make discovery channels look weak, even when they're driving the eventual conversions. ## 2. First Touch Attribution **The rule:** 100% of the credit goes to the **first marketing touch** in the journey. **Applied to our example:** ``` Meta ad (Day 1, T1) ████████████████████ 100% ↑ all credit here Google (Day 4, T2) ░░░░░░░░░░░░░░░░░░░░ 0% Newsletter (Day 7, T3) ░░░░░░░░░░░░░░░░░░░░ 0% ``` ### When to use it - **Top-of-funnel investment decisions** — First Touch is the only honest way to defend brand campaigns, paid social discovery, SEO content, and PR. These channels rarely convert on the last click, but the conversion wouldn't exist without them. - **Audience-discovery analysis** — "which channel originally found our best customers?" is a First Touch question. - **Content marketing reporting** — blog posts and pillar content are first-touch assets by design. ### When NOT to use it - **Optimising paid-search bidding** — First Touch will tell you to invest in awareness when you may need closing efficiency now. - **For short single-session journeys** — when the customer converts on their first visit, First Touch and Last Touch are the same thing. ## 3. Last Non-Direct Attribution **The rule:** 100% of the credit goes to the **last non-Direct touch**. If the final session before conversion was a Direct visit (the visitor typed your URL or clicked a bookmark), skip it and credit the marketing touch before it. **This is Google Analytics' default model** and SourceLoop's recommended default, because Direct visits usually aren't a marketing channel. **Applied to our example** (no Direct touches in the journey, so identical to Last Touch): ``` Meta ad (Day 1, T1) ░░░░░░░░░░░░░░░░░░░░ 0% Google (Day 4, T2) ░░░░░░░░░░░░░░░░░░░░ 0% Newsletter (Day 7, T3) ████████████████████ 100% ``` **The difference shows up when there IS a Direct touch:** ``` Meta ad (Day 1, T1) ░░░░░░░░░░░░░░░░░░░░ 0% Google (Day 4, T2) ░░░░░░░░░░░░░░░░░░░░ 0% Newsletter (Day 7, T3) ████████████████████ 100% ← Last Non-Direct credits here Direct (Day 10, T4) ░░░░░░░░░░░░░░░░░░░░ 0% ← Last Touch would credit this instead ``` ### When to use it - **GA-compatible reporting** — numbers line up with what your team sees in Google Analytics - **Default everyday view** — for most marketing-mix decisions, more honest than Last Touch because brand-recall direct visits don't deserve credit - **Brands with lots of repeat / Direct traffic** — particularly useful when your Direct percentage is high ### When NOT to use it - **When you specifically want to measure brand recall** — turning a paying customer into a direct-return visitor IS a marketing outcome; this model hides it. ## 4. First Non-Direct Attribution **The rule:** 100% of the credit goes to the **first non-Direct touch**. Same logic as Last Non-Direct, but at the start of the journey. **Applied to our example** (no Direct touches, so identical to First Touch): ``` Meta ad (Day 1, T1) ████████████████████ 100% Google (Day 4, T2) ░░░░░░░░░░░░░░░░░░░░ 0% Newsletter (Day 7, T3) ░░░░░░░░░░░░░░░░░░░░ 0% ``` **The difference shows up when the journey starts with a Direct visit:** ``` Direct (Day 0, T0) ░░░░░░░░░░░░░░░░░░░░ 0% ← First Touch would credit this Meta ad (Day 1, T1) ████████████████████ 100% ← First Non-Direct credits here Google (Day 4, T2) ░░░░░░░░░░░░░░░░░░░░ 0% Newsletter (Day 7, T3) ░░░░░░░░░░░░░░░░░░░░ 0% ``` ### When to use it - **Top-of-funnel reporting in established brands** — many returning customers' "first touch" is technically Direct (they've been to your site before). First Non-Direct credits the actual marketing channel that brought them back. - **First-touch attribution with cleaner data**, like First Touch, but without Direct dominating the report. ### When NOT to use it - **For brand-new audiences** — if your customer base is mostly first-time visitors, First Touch and First Non-Direct produce nearly identical results. ## 5. Linear Attribution **The rule:** Credit is split **equally across every touch** in the journey. **Applied to our example** (3 touches, so each gets 33.3%): ``` Meta ad (Day 1, T1) ███████░░░░░░░░░░░░░ 33% Google (Day 4, T2) ███████░░░░░░░░░░░░░ 33% Newsletter (Day 7, T3) ███████░░░░░░░░░░░░░ 33% ``` If the journey had 5 touches, each would get 20%. If 10 touches, each would get 10%. ### When to use it - **Cross-channel assist credit** — when you want to show that every channel contributed something, without arguing about which contributed more - **As a sanity check against single-touch models** — if Last Touch says a channel produced 50% of conversions and Linear says 18%, the difference is the credit it was over-claiming - **Executive summaries** — Linear is the easiest multi-touch model to explain in a meeting ### When NOT to use it - **For ad-platform bidding signals** — Google Ads and Meta won't optimise well off Linear conversions - **When recency genuinely matters more** — Linear weights a social impression three weeks ago the same as an email click 24 hours before conversion ## 6. Position-Based (U-Shaped) Attribution **The rule:** 40% to the **first touch**, 40% to the **last touch**, 20% split across the middle touches. **Applied to our example** (3 touches, so middle gets all 20%): ``` Meta ad (Day 1, T1) ████████░░░░░░░░░░░░ 40% ← first touch Google (Day 4, T2) ████░░░░░░░░░░░░░░░░ 20% ← middle Newsletter (Day 7, T3) ████████░░░░░░░░░░░░ 40% ← last touch ``` The name comes from the credit shape: big at both ends, smaller in the middle. The thinking is that the first touch (discovery) and the last touch (close) are the two structurally important moments; middle touches mostly serve to keep the lead warm. ### When to use it - **Balanced top-of-funnel + closing reporting** — a fair compromise for teams that want to credit both discovery channels and closing channels meaningfully - **B2B sales reporting** — the U-shape matches how most B2B funnels feel: discovery moment, long middle of nurture, closing moment - **Marketing-mix decisions involving cuts** — harder to defend cutting purely-middle channels, harder to over-credit pure-closing channels ### When NOT to use it - **For single-touch and two-touch journeys** — if there are no middle touches, Position-Based degenerates into 50/50 or 100% single-touch - **For very long sales cycles** — Time Decay is usually a better fit ## 7. Time Decay Attribution **The rule:** Credit is weighted by recency. The closer a touch is to the conversion, the more credit it gets. SourceLoop uses **exponential decay with a 7-day half-life**: a touch 7 days before conversion gets half the credit of a touch on the same day. **Applied to our example** (conversion on Day 14): ``` Meta ad (Day 1, 13 days out) ███░░░░░░░░░░░░░░░░░ ~13% Google (Day 4, 10 days out) ██████░░░░░░░░░░░░░░ ~30% Newsletter (Day 7, 7 days out) ███████████░░░░░░░░░ ~57% ↑ closest to conversion ``` The 7-day half-life means: | Days before conversion | Credit weight (relative) | |---|---| | 0 days (same day) | 100% | | 7 days | 50% | | 14 days | 25% | | 21 days | 12.5% | | 28 days | 6.25% | So even a touch 4 weeks out still gets some credit, just much less than recent touches. ### When to use it - **Long B2B / SaaS sales cycles** — when journeys span weeks or months, Time Decay correctly reflects that the touch from yesterday is more relevant than the touch from a month ago - **High-consideration ecommerce** — for purchases involving multiple research sessions, Time Decay credits late-funnel research moments over early-funnel discovery - **As a smarter Last Touch** — Time Decay is essentially Last Touch with grace for very recent assists. Fairer when the last week of touches all matter, not just the final click. ### When NOT to use it - **For short single-session conversions** — if most journeys are same-day, Time Decay collapses to Last Touch - **When measuring top-of-funnel investment** — Time Decay will always under-credit the discovery touch from weeks ago ## Side-by-side: same journey, all 7 models The headline comparison from earlier, repeated here as a take-home reference: | Model | Meta (T1, Day 1) | Google (T2, Day 4) | Email (T3, Day 7) | |---|---|---|---| | Last Touch | 0% | 0% | **100%** | | First Touch | **100%** | 0% | 0% | | Last Non-Direct | 0% | 0% | **100%** | | First Non-Direct | **100%** | 0% | 0% | | Linear | 33% | 33% | 33% | | Position-Based (U-Shaped) | **40%** | 20% | **40%** | | Time Decay | ~13% | ~30% | **~57%** | Notice how the same conversion produces wildly different stories: - Under **Last Touch**, the campaign is "Newsletter wins, kill Meta and Google". - Under **First Touch**, the campaign is "Meta is the hero, Newsletter and Google just happened to be in the way". - Under **Position-Based**, "discovery and closing both matter, the middle is supporting". - Under **Time Decay**, "the last week of touches mostly drove this, the Meta impression two weeks back contributed something but barely". All four are technically correct. The right one depends on what you're deciding. ## What this looks like in SourceLoop Every dashboard in SourceLoop has an attribution model selector in the top-right of the filter bar. It defaults to **Last Touch**, but you can change it (or stack multiple models) on every page. ![SourceLoop Traffic dashboard with the Last Touch attribution model selector visible in the top-right of the filter bar](/help/screenshots/sourceloop-traffic-dashboard-chart.webp) The selector is the same control across the Traffic, Content, Locations, Devices, Paths, and Ads Performance dashboards. Picking a different model rebuilds every number on the page (the metric tiles, the chart, the breakdown table) under the rules of the model you picked. To compare two models in the same view, click **+ Add model** in the selector. The breakdown table will split each metric column per model so you can see Last Touch and First Touch credit side by side for the same campaigns. The numerical gap between the two columns is your assist value. ![SourceLoop attribution dashboard showing channel-level conversion breakdown that updates as the attribution model changes](/help/screenshots/sourceloop-attribution-dashboard.webp) ## Which model should you actually pick? For most teams the answer isn't a single model. It's **which model for which question**. | Decision you're making | Model to use | |---|---| | Bidding signal for Google Ads, Meta, TikTok | **Last Touch** (or Last Non-Direct) | | Quarterly executive update | **Position-Based** or **Linear** | | Content / SEO reporting | **First Touch** or **First Non-Direct** | | "Should we cut this channel?" debate | Compare **Last Touch + Linear** side by side | | Long B2B sales cycle reporting | **Time Decay** | | Default everyday view | **Last Non-Direct** (matches GA) | The Traffic, Content, Locations, Devices, Paths, and Ads Performance dashboards in SourceLoop all support multiple models simultaneously via the model selector. The cleanest workflow is to **stack two models** in the same view, the gap between their columns is the actual answer to "how much of this channel is direct conversion vs assist". ## How to switch models in SourceLoop On any dashboard: 1. Click the model selector top-right (showing "Last touch" by default). 2. Click **+ Add model** to stack a second (or third) model alongside the current one. 3. Metric columns split per model, so a single table can show Last Touch conversions, First Touch conversions, and Linear conversions side by side. 4. Use the **X** next to a model to remove it. The model applies to Conversions counts, Revenue totals, and derived metrics (CPA, ROAS). Switching models changes every number on the dashboard, the chart, the tiles, the table, and the totals. ## What's next - **See the dashboards where these models apply:** - [Traffic dashboard](/help/traffic-dashboard/) - [Content dashboard](/help/content-dashboard/) - [Locations dashboard](/help/locations-dashboard/) - [Devices dashboard](/help/devices-dashboard/) - [Paths dashboard](/help/paths-dashboard/) - [Ads Performance dashboard](/help/ads-performance-dashboard/) - **See the actual touch sequences** multi-touch models work on: [Paths dashboard](/help/paths-dashboard/) ## Frequently Asked Questions ### What's an attribution model? A rule for splitting credit when one conversion is preceded by multiple marketing touches. A customer might see your Meta ad, then search Google, then click a newsletter link before buying. An attribution model decides which of those three touches deserves credit. Different models give different answers; the choice depends on the decision you're making. ### What's the difference between single-touch and multi-touch attribution? Single-touch gives 100% credit to one touchpoint, either the first or the last. Multi-touch splits credit across every touch in the journey using a weighting rule. Single-touch is simpler and matches what ad platforms expect; multi-touch is more honest about how marketing actually works, but harder to defend cleanly to a single channel. ### Which attribution model should I use? Depends on the decision. For paid bidding signals, Last Touch. For brand awareness justification, First Touch. For everyday reporting that matches Google Analytics, Last Non-Direct. For honest cross-channel credit, Linear. For balancing top and bottom of funnel, Position-Based (U-Shaped). For long sales cycles where recency matters, Time Decay. ### Does my attribution model affect the numbers in Google Ads or Meta dashboards? No. Each ad platform applies its own model to the conversions you send back to it. Your SourceLoop model affects SourceLoop dashboards and the conversion data pushed back, but the platform's UI still reports under its own rules. ### Why does Google Analytics use Last Non-Direct as the default? Because Direct visits usually aren't a marketing channel, they're someone typing your URL or clicking a bookmark. Crediting the marketing touch before the Direct visit is more useful than crediting Direct itself. SourceLoop defaults to Last Non-Direct for the same reason. ### Can I look at one campaign under multiple attribution models at the same time? Yes. SourceLoop's model selector supports stacking multiple models in the same view, so you'll see Last Touch credit alongside First Touch credit (or however many others you add) for the same campaign in the same table. The gap between columns is the assist value. --- # How to track lead source in Web Form submission Capture the marketing source and pre-conversion journey behind every form submission. Automated for embedded forms, semi-automated for hosted form tools. Source: https://sourceloop.ai/help/track-web-form-submissions/ Updated: 2026-05-29 --- Web forms are the most common conversion type SourceLoop captures. Once the tracker is on your site, **every form submission is enriched with marketing source, UTMs, landing page, and the visitor's full pre-submit journey**, so when a lead lands in your dashboard you immediately see what brought them in. This article is the overview. For setup specifics on individual form tools, the left sidebar has a per-tool article for every supported builder. ## Step 1: Install the SourceLoop tracking script The tracker is the prerequisite for both capture paths below. It records every visitor's session, UTMs, landing page, and journey, so when a form submission arrives (directly from the browser or later via a webhook), SourceLoop can stitch it to the right visitor. 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Tracking code** in the left sidebar. 3. Copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) 4. Paste it into the `` of every page on your site, especially the pages where forms (or the buttons that open hosted forms) appear. Once the tracker is live, head to the matching section below depending on whether your form is embedded on your own site (automated) or hosted on the form tool's domain (semi-automated). ## How form capture works SourceLoop supports two capture paths, depending on where your form actually lives: | Path | What it means | Setup | |---|---|---| | **Automated** | The form is embedded on a page that loads the SourceLoop tracker, the browser fires the submit, the tracker reads the email field. | Install the tracker. That's it. | | **Semi-automated** | The form is hosted off-site (e.g., `typeform.com/...`, `jotform.co/...`) so the tracker never sees the submit. The form tool forwards each submission to SourceLoop via a webhook. | Install the tracker, add SourceLoop's webhook URL inside the form tool, pass the visitor's identifier through the form URL. | Both paths produce the same end result: a lead in the **Contacts Hub** with full first-touch and last-touch attribution, the converting landing page, and the entire pre-submit session timeline. ### Why two paths Forms hosted on your own domain (HTML, Webflow, Framer, every WordPress plugin) submit through the browser. The SourceLoop tracker is already running on that page, so it can read the email field directly as the submission fires. No extra wiring needed. Forms hosted on the form tool's own domain (Typeform's hosted page, JotForm's hosted page, Fillout's hosted page) submit on a different origin. The visitor leaves your site before the form fires its submit event, so the tracker never sees it. The semi-automated webhook fills that gap. Before the visitor leaves your site to open the hosted form, the tracker quietly passes the visitor's anonymous identifier along on the form URL. When the visitor submits, the form tool sends a webhook to SourceLoop with the submission data plus that identifier. SourceLoop stitches the submission back to the visitor's pre-submit journey server-side, so the lead arrives with the same full attribution as an automated capture would have produced. ## Automated capture: supported tools Tracker on the page is the only setup. Pick the matching article for the small confirmation steps.
[HTML forms](/help/track-lead-source-in-html-forms/) [HubSpot Forms](/help/track-lead-source-in-hubspot-forms/) [Webflow](/help/track-lead-source-in-webflow/) [Framer](/help/track-lead-source-in-framer/) [Tally](/help/track-lead-source-in-tally/) [Elementor](/help/track-lead-source-in-elementor/) [Gravity Forms](/help/track-lead-source-in-gravity-forms/) [WPForms](/help/track-lead-source-in-wpforms/) [Contact Form 7](/help/track-lead-source-in-contact-form-7/) [Fluent Forms](/help/track-lead-source-in-fluent-forms/) [Ninja Forms](/help/track-lead-source-in-ninja-forms/) [Forminator](/help/track-lead-source-in-forminator-forms/) [Everest Forms](/help/track-lead-source-in-everest-forms/) [Formidable Forms](/help/track-lead-source-in-formidable-forms/) [JetFormBuilder](/help/track-lead-source-in-jetformbuilder/) [Kali Forms](/help/track-lead-source-in-kali-forms/) [MetForm](/help/track-lead-source-in-metform/) [SureForms](/help/track-lead-source-in-sureforms/) [HappyForms](/help/track-lead-source-in-happyforms/) [WS Form](/help/track-lead-source-in-ws-form/) [weForms](/help/track-lead-source-in-weforms/) [Cognito Forms](/help/track-lead-source-in-cognito-form/) [ConvertKit](/help/track-lead-source-in-convertkit/) [Klaviyo](/help/track-lead-source-in-klaviyo/) [Omnisend](/help/track-lead-source-in-omnisend/) [Mailchimp for WP](/help/track-lead-source-in-mailchimp-for-wp/) [AidaForm](/help/track-lead-source-in-aidaform/) [ARForms](/help/track-lead-source-in-arforms/) [GoHighLevel](/help/track-lead-source-in-gohighlevel-form/) [involve.me](/help/track-lead-source-in-involve-me/)
## Semi-automated capture: supported tools Hosted on the form tool's own domain. Install the tracker on your site (so the visitor's pre-submit journey is captured), then wire a webhook inside the form tool and add SourceLoop's anonymous-id parameter to the form URL.
[Typeform](/help/track-lead-source-in-typeform/) [JotForm](/help/track-lead-source-in-jotform/) [Fillout](/help/track-lead-source-in-fillout/) [Formstack](/help/track-lead-source-in-formstack/) [Formsite](/help/track-lead-source-in-formsite/) [FormAssembly](/help/track-lead-source-in-formassembly/) [Formester](/help/track-lead-source-in-formester/) [123FormBuilder](/help/track-lead-source-in-123formbuilder/) [QuestionScout](/help/track-lead-source-in-questionscout/) [Zoho Forms](/help/track-lead-source-in-zoho-forms/) [YouForm](/help/track-lead-source-in-youform/) [MightyForms](/help/track-lead-source-in-mightyforms/) [Quill Forms](/help/track-lead-source-in-quill-forms/) [Paperform](/help/track-lead-source-in-paperform/)
## Verify it's working Once the tracker is installed (and the webhook is configured, for semi-automated tools): 1. Open the page that hosts your form in an **incognito window**. 2. Append `?utm_source=test&utm_medium=verify&utm_campaign=form-check` to the URL. 3. Fill in the form with a real email you can access and submit. 4. Within seconds, the lead should appear at the top of the **Contacts Hub** at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with the three test UTM values, the converting landing page, and the full pre-submit timeline. If the test submission doesn't appear, open the matching per-tool article from the sidebar and walk the verification step there. The most common cause is the tracker not loading on the page that hosts the form. ## Don't see your form tool? If your form is a plain `` element embedded on a tracked page, capture works automatically, no per-tool article needed. If your form uses a custom JavaScript submit handler (an AJAX submit, a React form library, a Vue component that hijacks the submit event), call SourceLoop's tracker directly in your success handler: ```js window.SourceLoop?.track("form_submit", { email: "lead@example.com", formId: "contact-us", custom: { plan: "Pro", company: "Acme Co." }, }); ``` The `email` field is required (it's how SourceLoop dedups the lead). Everything else is optional and lands as custom data on the lead in the Contacts Hub. ## Where lead source shows up - **Contacts Hub** — every form submission becomes a row with full first-touch and last-touch source plus the pre-submit timeline. See [Contacts (Leads) table](/help/contacts-leads-table/). - **Traffic dashboard** — group submissions by channel, source, medium, campaign, landing page. See [Traffic dashboard](/help/traffic-dashboard/). - **Funnels** — build a funnel ending in "Web form submission" to find the highest-converting paths from first visit to form fill. See [Conversion funnels guide](/help/conversion-funnels-guide/). For paid acquisition feeding form submissions, mirror leads back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real qualified leads instead of vanity clicks. See [Connect Google Ads](/help/connect-google-ads/) for the wiring. ## Frequently Asked Questions ### What's the difference between automated and semi-automated form capture? Automated means SourceLoop captures the submission entirely from the visitor's browser, no setup needed in your form tool. It works when the form is embedded on a page that loads the SourceLoop tracker. Semi-automated means the form tool hosts the form on its own domain (so the tracker can't see the submit), and you wire a webhook in the form tool to forward each submission to SourceLoop. Both paths give you full marketing source and journey on the lead. ### My form tool isn't listed. Can I still track it? Yes. Any HTML form embedded on a page where the SourceLoop tracker runs is captured automatically. For tools that host the form on their own domain and don't offer webhooks, see the Custom form tracking section for the JS snippet to fire on submission success. ### Do I need to add UTM parameters to capture lead source? No. UTM parameters help SourceLoop attribute paid campaigns precisely, but they're not required. The tracker also resolves source from the referrer, click IDs (gclid, fbclid, msclkid, li_fat_id), and channel rules. Untagged visits land as Direct or Organic. ### Can SourceLoop attribute form fills back to specific Google Ads campaigns or Facebook ads? Yes. SourceLoop captures the click ID on the landing session (gclid for Google Ads, fbclid for Facebook and Instagram, msclkid for Microsoft Ads, li_fat_id for LinkedIn) and stores it on the lead. Each form fill in the Contacts Hub shows the exact campaign, ad group, keyword, or creative that produced the click, and the Traffic dashboard groups every form fill by paid platform and campaign. ### Will the same lead be captured twice if the form is set to redirect after submit? No. SourceLoop dedups submissions by email within a short window, so a quick redirect, a submit retry, or a hidden duplicate form on the page won't create two leads. ### Does this work for multi-step forms? Yes. SourceLoop captures the lead the moment the final step is submitted (when the email arrives). Intermediate step transitions don't trigger a capture. ### My form uses AJAX and never fires a real submit event. What do I do? Use the SourceLoop JavaScript API in your success handler, see Custom form tracking below. Pass the email as the required field; everything else is optional. This works regardless of how the form library handles the submission internally. --- # How to track lead source in Typeform Connect every Typeform submission back to the channel that produced it: ad, post, email, or podcast, with the visitor's full journey on each lead. Source: https://sourceloop.ai/help/track-lead-source-in-typeform/ Updated: 2026-05-28 --- Typeform is the go-to form builder for teams who care about how a form feels: conversational layouts, smooth transitions, branded design. Marketing and product teams use it for everything from waitlist signups to demo requests to NPS surveys. What Typeform doesn't surface, though, is **which marketing investment produced each submission**. This guide fills that gap. The setup runs four steps and takes about ten minutes. The webhook step requires Typeform on a paid plan (Basic or higher). ## What SourceLoop captures from Typeform After setup, every Typeform submission lands in SourceLoop with: - **First-source channel**: the discoverable origin (which campaign, ad, or referral first brought the visitor in) plus the full UTM parameter set - **Path through your site** ahead of the form submission, every page in order - **Time on site before submission**, summed across sessions - **Sessions to conversion**: how many separate visits the respondent made before submitting - **Respondent email** captured from the Typeform fields - **Original landing destination** and the referring URL from session one - **Source of the converting session** (which often differs from the very first-touch source) - **Device, geography, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where you'll embed the Typeform - A **Typeform account on a paid plan** (Basic or higher, for webhook support) with at least one form published ## Step 1: Install the SourceLoop tracking script Open SourceLoop, click **Setup** in the sidebar, and choose the **Tracking code** tab. Copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet into the `` of every page on your site. Site-wide installation is best, that way SourceLoop sees a respondent's complete journey, not just their visit to the form page. ## Step 2: Add the sl_aid URL parameter to your Typeform Typeform's **Workflow** tab is where you pull data from URL parameters into the form. Add one parameter called `sl_aid` so SourceLoop can match each submission to the visitor's tracked journey. 1. From your workspace, open the form you want to track. ![Typeform workspace with a form selected](/help/screenshots/htbqxhz8w8.png) 2. Inside the form builder, click the **Workflow** tab in the top navigation. ![Typeform builder with the Workflow tab highlighted](/help/screenshots/auulps0ofiq.png) 3. Click the **+** button on the "Pull data in" card. ![Typeform Workflow tab showing the Pull data in card](/help/screenshots/qpbw57c0l7s.png) 4. Click **Add new parameter**. ![Typeform parameter configuration with Add new parameter button](/help/screenshots/9r7gv6sl5gv.png) 5. Add `sl_aid` as the parameter name and save. ![Typeform parameter name set to sl_aid](/help/screenshots/4o02gbkucrf.png) That's the only parameter you need. SourceLoop fills in the channel, source, journey, and other attribution details on its side using this single identifier. ## Step 3: Embed the Typeform on your site The form needs to live on a page where the SourceLoop tracking script is already running. 1. At the top of your Typeform editor, click the **Share** button. ![Typeform editor with the Share button highlighted](/help/screenshots/tiv4147tiz.png) 2. Choose **Embed on website**. ![Typeform Share dialog with Embed on website option](/help/screenshots/x7ymelpqhkr.png) 3. Click **Start embedding** and pick the embed style (inline, popup, slider, side tab, full page). Any of them works for attribution. ![Typeform embed picker showing multiple embed styles](/help/screenshots/b0m5zuc6p5a.png) 4. Copy the embed code and paste it onto the page on your site where you want submissions to happen. ![Typeform embed code ready to copy](/help/screenshots/f4eq2pr9s2.png) > **Direct Typeform links can't be attributed** > Submissions made through a raw `.typeform.com/to/` link, the kind you'd drop into an email blast or paste into a tweet, **won't carry attribution data**. The respondent never lands on a page with the tracker, so the `sl_aid` parameter never gets a value. Route campaigns through a landing page that embeds the Typeform. ## Step 4: Configure the Typeform webhook The webhook is what delivers each Typeform response to SourceLoop, where it gets matched to the visitor's journey and turned into a fully-attributed lead. 1. In SourceLoop, go to **Setup -> Incoming Webhooks** and copy your webhook URL. ![SourceLoop Setup page on the Incoming Webhooks tab](/help/screenshots/rupclnrhxpj.png) 2. Inside the Typeform builder, click the **Connect** tab in the top navigation. ![Typeform builder with the Connect tab highlighted](/help/screenshots/nm3pmtseao.png) 3. Under Connect, click the **Webhooks** tab. ![Typeform Connect section showing the Webhooks tab](/help/screenshots/m1ow7ep1nab.png) 4. Click **Add a webhook**, paste the SourceLoop webhook URL, and save. ![Typeform Add a webhook dialog with the webhook URL field](/help/screenshots/em55x2fpvs8.png) Submit a test response on your embedded form to confirm the conversion arrives in SourceLoop as a form submission with channel, source, and landing page populated. ## Where to see Typeform submissions in SourceLoop Once the integration is live, your Typeform data shows up across three SourceLoop views: ### Contacts Hub Open [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) and each Typeform submission appears as a contact row. Click any row to unpack the respondent's full pre-submission timeline: the channel that brought them in, the content they consumed, and how many sessions they took before filling the form. ![SourceLoop Contacts Hub showing a Typeform respondent with the full pre-submission journey expanded](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Open [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to roll up your Typeform submissions by source, medium, and campaign. Useful for answering questions like "is our LinkedIn paid campaign generating qualified leads, or just traffic?" ![SourceLoop attribution dashboard with Typeform submissions broken down by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Head to [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) and configure a funnel ending in "Typeform submission". Cut it by source or by landing page to see which paths actually convert visitors into completed forms. ![SourceLoop funnel report ending in a Typeform submission conversion step](/help/screenshots/sourceloop-funnel.png) For teams running paid acquisition, push the Typeform submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms optimize for completed forms instead of clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through that wiring. ## Frequently Asked Questions ### Does this work on Typeform's Free plan? The hidden-field and embed steps work on every Typeform plan, but the webhook step requires a paid plan (Basic and above). On Free, you can still set up the parameter but submissions won't reach SourceLoop without webhooks enabled. ### My form uses Logic Jumps and conditional pages. Does that affect tracking? No. The setup is independent of the form's internal flow. Logic Jumps, hidden questions, conditional branches, and multi-page layouts all work the same way for attribution. ### What if I share my Typeform via its direct typeform.com link instead of embedding? Submissions made via a direct `.typeform.com/to/` link won't carry attribution data, because the visitor never lands on a tracked page first. Embed the form on your own site to capture those leads with their source. ### Will SourceLoop conflict with Typeform's native HubSpot, Mailchimp, or Slack integrations? No. SourceLoop runs alongside Typeform's connectors without touching them. Your existing automations continue to fire, and SourceLoop adds attribution data on top. ### Does adding SourceLoop slow down the Typeform embed? No. The SourceLoop script is small and loads asynchronously, so it has no measurable impact on how quickly the Typeform iframe initializes or responds. --- # How to track lead source in HubSpot Forms Add real attribution to every HubSpot form submission, so reports show which ad, search, or content generated each lead instead of Direct. Source: https://sourceloop.ai/help/track-lead-source-in-hubspot-forms/ Updated: 2026-05-28 --- HubSpot Forms is the default lead-capture surface for any team running HubSpot CRM, free or paid, marketing or sales. The data it sends back to HubSpot is solid for contact info, but the source attribution often shows up as a generic "Direct" or "Organic Search" entry, with very little detail about which campaign or content actually drove the lead. SourceLoop layers on the missing data: full UTM details, every page the prospect viewed, and which session ultimately converted them. Setup takes about five minutes, works on every HubSpot plan, and runs alongside (not against) HubSpot's own analytics. ## What SourceLoop captures from HubSpot Forms After setup, each HubSpot form submission lands in SourceLoop with: - **Acquisition source** with full UTM parameters captured at the visitor's first session - **Browsing trail** before submission, every page in chronological order - **Total time invested on your site** before the form fill - **Number of distinct sessions** before the conversion - **Email and name** captured from the HubSpot form fields - **Landing page** and the referring URL from the very first session - **Last-session source**, the channel that ushered them to the submission - **Device type, country, and browser** for segment-level analysis ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website (or HubSpot CMS pages) where your HubSpot forms are embedded - A **HubSpot account** (any tier) with at least one form configured under **Marketing -> Forms** ## Step 1: Install the SourceLoop tracking script Inside SourceLoop, click **Setup** in the left navigation and pick the **Tracking code** tab. Copy the snippet shown. ![SourceLoop Setup page showing the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) If your forms live on **your own site** (WordPress, Webflow, Framer, custom), paste the snippet inside the `` tag of every page. If your forms live on **HubSpot CMS pages**, open HubSpot, head to **Marketing -> Files and Templates -> Templates -> Site header HTML**, and paste the snippet there. HubSpot will inject it into every page on your hosted domain. ## Step 2: Embed your HubSpot form on a tracked page In HubSpot, open your form and copy the embed code from **Embed -> Get embed code**. Drop it onto the page where you want submissions to happen. A few common HubSpot form scenarios: - **Inline embed** on a marketing landing page: the form renders directly in the page body - **Popup form**: triggered by exit intent, scroll depth, or a button click - **Native HubSpot CMS form module**: when your page is built inside HubSpot, the form is added through HubSpot's page editor All three work the same way for attribution. The non-negotiable: the page hosting the form must also have the SourceLoop snippet from step 1. > **Forms shared via HubSpot shareable links aren't trackable** > If you use HubSpot's "Share" link, the `share.hubspot.com/...` URL that exposes the form on a HubSpot-hosted page outside your site, **those submissions won't appear in SourceLoop**. The respondent never lands on a page that has your tracker. Embed the form on your own domain to capture that traffic. ## Step 3: Verify it's working Open the form page in an **incognito tab**, add `?utm_source=test&utm_medium=verify&utm_campaign=hubspot-form-check` to the URL, and submit a test entry using a real email you control. Open the **Contacts Hub** in SourceLoop, your test submission should appear in a few seconds with the test UTM parameters attached to the contact record. ## Where to see HubSpot form submissions in SourceLoop Three SourceLoop surfaces give complementary views of your HubSpot form data: ### Contacts Hub [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) shows each form submission as a contact row. Expanding a row reveals the prospect's entire pre-submission timeline, the entry channel, the pages they viewed, and how long they evaluated before filling the form. ![SourceLoop Contacts Hub showing a HubSpot form submission lead with their full pre-submission journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls up your HubSpot form submissions by source, medium, and campaign. This is the report you bring to the next marketing review when someone asks "what's actually generating qualified leads?" ![SourceLoop attribution dashboard with HubSpot form submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), configure a funnel that ends in "HubSpot form submission". Slice it by channel or landing page to spot the highest-converting acquisition paths. ![SourceLoop funnel report ending in a HubSpot form submission conversion step](/help/screenshots/sourceloop-funnel.png) If your marketing program runs paid acquisition, forward the HubSpot form submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real lead generation rather than vanity click counts. [Connect your Google Ads account](/help/connect-google-ads/) covers that setup. ## Frequently Asked Questions ### Doesn't HubSpot already track lead source on contacts? HubSpot's built-in `hs_analytics_source` is helpful but coarse and often defaults to "Direct" when UTMs aren't pristine. SourceLoop captures the full journey including UTM details, all pages visited, and first/last-session sources, then keeps that data alongside HubSpot's own properties without overwriting them. ### Does this work with HubSpot Free? Yes. Whichever HubSpot tier you're on, Free through Enterprise, the form-embedding flow is the same. SourceLoop attaches attribution data to every submission made through an embedded HubSpot form on a tracked page. ### My HubSpot forms run on landing pages built with HubSpot's CMS. Does that work? Yes, as long as the SourceLoop tracking script is loaded on those HubSpot CMS pages. You can add it through HubSpot's site settings under Marketing -> Files and Templates -> Site header HTML. ### Will SourceLoop overwrite or interfere with HubSpot's standard contact properties? No. SourceLoop stores its attribution data on its own contact record. HubSpot's properties (`hs_analytics_source`, `hs_latest_source`, etc.) stay untouched. If you'd like the data flowing into HubSpot contact records, set that up via the HubSpot CRM sync. ### I use the HubSpot non-HubSpot Forms feature (collecting submissions from third-party forms). Does that work? SourceLoop tracks the form submission on the page where the form lives. If your third-party form lives on a SourceLoop-tracked page, the submission is captured normally regardless of whether HubSpot is also picking it up. --- # How to track lead source in Jotform Get full attribution on every Jotform submission, with channel, source, campaign, click ID, and landing page captured as hidden fields in the form. Source: https://sourceloop.ai/help/track-lead-source-in-jotform/ Updated: 2026-05-28 --- Jotform is the workhorse of online forms: HR intake, healthcare onboarding, education, government, you name it. It's powerful and flexible, but the source data flowing to your downstream tools is usually just "Web Form Filled", with no detail about which marketing channel actually produced the submission. This guide makes the attribution data part of every form submission itself. Four steps, about ten minutes total. Works on every Jotform plan including Free. ## What SourceLoop captures from Jotform After setup, every Jotform submission arrives in your destination tools (and in SourceLoop) with these attribution values populated as hidden fields: - **Channel** + **Latest Source**, **Medium**, **Campaign**, **Term**, **Content** (the closing session's UTM details) - **Latest Landing Page** (where the closing session began) - **Click IDs** for paid ads (`gclid`, `fbclid`, `msclkid`, `li_fat_id`, `ttclid`) - Optional **First Channel / Source / Medium / Campaign / Term / Content / Landing Page** for multi-touch attribution - The respondent's regular form fields (email, name, etc.) flow through unchanged ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the Jotform will be embedded - A **Jotform account** (any plan) with at least one form built ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup** from the left sidebar, and click the **Tracking code** tab. Copy the snippet on display. ![SourceLoop Setup page with the tracking code snippet to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add the snippet to the `` of every page on your site. Site-wide coverage gives SourceLoop the full picture of the respondent's journey, not just their visit to the form page. ## Step 2: Add latest-touch hidden fields to your Jotform For each field below, repeat the same workflow: add a Short Text field, name it, mark it hidden, and set the Unique Name. The Unique Name is what matters, that's how SourceLoop knows which attribution value to put where. **The repeatable workflow:** 1. Open your form in the Jotform Form Builder. ![Jotform Form Builder with a form open](/help/screenshots/bw407qgz5hv.png) 2. Click **Add Form Element** on the left sidebar. ![Jotform Add Form Element panel](/help/screenshots/cp8dyftt8u.png) 3. Add a **Short Text** field. ![Jotform Short Text field added to the form](/help/screenshots/ew305hansff.png) 4. Open the field's **Properties** panel and name the field per the table below. ![Jotform field Properties panel](/help/screenshots/ydin2555csf.png) 5. Navigate to the **Advanced** tab, enable **Hide Field** so visitors don't see it, then set the **Unique Name** per the table below. ![Jotform field Advanced tab with Hide Field and Unique Name configuration](/help/screenshots/q77hods72ul.png) **Latest-touch fields (recommended baseline):** | Field Label | Unique Name | | --- | --- | | Channel | `channel` | | Latest Source | `attribution_source` | | Latest Medium | `attribution_medium` | | Latest Campaign | `attribution_campaign` | | Latest Term | `attribution_term` | | Latest Content | `attribution_content` | | Latest Landing Page | `landingpage` | **Click ID fields (only if you run paid ads on these networks):** | Field Label | Unique Name | | --- | --- | | Google Click ID | `gclid` | | Facebook Click ID | `fbclid` | | Microsoft Click ID | `msclkid` | | LinkedIn Click ID | `li_fat_id` | | TikTok Click ID | `ttclid` | > **Use the exact Unique Name values** > The Unique Name is what gets matched. If you label the field differently from the table or use a different unique name, the value won't populate. Field Labels can be anything you want, but the Unique Name has to match exactly. ## Step 3: Track first-touch attribution (optional) Skip this step unless you want multi-touch attribution, the ability to compare the original acquisition channel against the most recent one. If you do, repeat the same workflow from step 2 for each field below: | Field Label | Unique Name | | --- | --- | | First Channel | `first_channel` | | First Source | `first_source` | | First Medium | `first_medium` | | First Campaign | `first_campaign` | | First Term | `first_term` | | First Content | `first_content` | | First Landing Page | `first_landingpage` | With both touch sets in place, your Jotform submissions carry the complete picture: which channel originally found the prospect, and which channel finally converted them. ## Step 4: Embed the Jotform on your website The form needs to live on a page where the SourceLoop tracking script is already running. 1. In Jotform, click **Publish** at the top of the form builder. ![Jotform Publish button highlighted at the top of the builder](/help/screenshots/rsx24qylmi.png) 2. Select **Embed** from the left menu, then pick either **iFrame** or **JavaScript**. ![Jotform Embed options with iFrame and JavaScript](/help/screenshots/pfoy8z5qtyg.png) 3. Copy the embed code and paste it on your site where you want the form to appear. Now, every time someone completes the form, the attribution values appear alongside the rest of the answers in the form submission. ![Jotform submission with attribution hidden field values populated](/help/screenshots/vs1ukq6cisr.png) > **Direct Jotform URLs aren't attributable** > Submissions made through a raw `form.jotform.com/` link or your hosted Jotform page **won't carry attribution data**. The respondent never touches a tracked page, so the hidden fields stay empty. Route those campaigns through a landing page on your own site that embeds the form. ## Where to see Jotform submissions in SourceLoop Beyond the attribution data being inside each Jotform submission itself, SourceLoop also gives you three dedicated views: ### Contacts Hub Each Jotform submission becomes a row in the Contacts Hub at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand a row to see the visitor's full pre-submission timeline: the campaign that brought them in, the pages they browsed, and how long they took before submitting. ![SourceLoop Contacts Hub with a Jotform submission expanded to show the lead's full pre-submission journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Pull up [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to see Jotform submissions grouped by source, campaign, and landing page. Great for spotting which channels generate qualified leads versus those that produce traffic but no conversions. ![SourceLoop attribution dashboard with Jotform submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Jotform submission". Cut it by source, campaign, or landing page to see your most-converting paths. ![SourceLoop funnel report ending in a Jotform submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is part of your strategy, forward the Jotform submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms can optimize against real lead generation. [Connect your Google Ads account](/help/connect-google-ads/) walks through that setup. ## Frequently Asked Questions ### Does this work with Jotform's Free plan? Yes. The setup is entirely on the form-builder side and on your website, no webhooks or API connections involved. Every Jotform plan supports hidden fields. ### Why are the attribution values shown as hidden fields instead of being added separately? This is the "manual" integration pattern, the attribution data lives directly on the Jotform submission. That way it flows automatically into whatever downstream system you've connected (CRM, Zapier, Make, email autoresponder) without needing a separate webhook for SourceLoop. ### Do I have to add every field? That's a lot of hidden inputs. No. The latest-touch fields in step 2 are recommended for a baseline view of attribution. The click-ID fields are only needed if you run paid ads on those networks. First-touch fields in step 3 are optional and only matter if you want multi-touch attribution. Skip what doesn't apply. ### What happens to the hidden fields when someone fills the form? They're populated automatically as soon as the form loads on a page where the SourceLoop tracker is running. The respondent never sees them, but the values come through on the submission alongside the visible answers. ### I share my Jotform via its direct form.jotform.com link. Will those submissions carry attribution? No. Submissions made through a raw `form.jotform.com/` link won't carry attribution data, because the visitor never lands on a page with the tracker. To attribute that traffic, embed the form on a landing page that has the SourceLoop snippet. --- # How to track lead source in 123FormBuilder Tie every 123FormBuilder submission back to the marketing channel that produced it, with the full pre-submission journey saved on each lead. Source: https://sourceloop.ai/help/track-lead-source-in-123formbuilder/ Updated: 2026-05-28 --- 123FormBuilder is the form tool of choice for organizations that need serious flexibility, conditional logic, payment integrations, multi-step forms, native HIPAA-eligible plans, all without writing code. The integration with SourceLoop is unique compared to most other form builders: instead of a webhook or hidden field setup, 123FormBuilder uses a small helper script that runs inside the form itself. Three steps, about five minutes start to finish. ## What SourceLoop captures from 123FormBuilder Every form submission ends up in SourceLoop with the visitor's full marketing context: - **Acquisition channel** plus complete UTM parameter set from the first session - **Pre-submission browsing trail**, every page in chronological order - **Time on site** before the form was submitted - **Visit count** leading up to the conversion - **Email and name fields** from the 123FormBuilder submission - **Original landing page** and the URL that referred the visitor - **Source of the closing session** (the one that produced the submission) - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the 123FormBuilder form will be embedded - A **123FormBuilder account** with at least one form built - Access to 123FormBuilder's **Set up -> Advanced** options (varies by plan, check before starting) ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup** from the left navigation, and click the **Tracking code** tab. Copy the snippet displayed. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add the snippet to the `` of every page on your site. Especially the page where you'll embed the 123FormBuilder form, that's where the magic needs to happen. ## Step 2: Add the SourceLoop helper script to your form 123FormBuilder needs a small helper script running inside the form to connect it to SourceLoop. Adding it takes about thirty seconds. 1. Open your form in 123FormBuilder. 2. Click the **Set up** tab at the top and choose **Advanced**. ![123FormBuilder Set up tab with Advanced highlighted](/help/screenshots/z98e7mbfffh.png) 3. Check the **Add a JS script to your form** option and paste this URL into the field: ``` https://app.sourceloop.ai/123formbuilder-integration.js ``` ![123FormBuilder Advanced settings with the JS script URL pasted in](/help/screenshots/tf4dczq2q7r.png) 4. Click **Save**. That's everything on the 123FormBuilder side. The helper script handles the rest, ferrying submission data into SourceLoop along with the visitor's marketing context. ## Step 3: Embed the form on your website The form needs to live on a page where the SourceLoop tracking script is also running. 1. In 123FormBuilder, click **Publish** and choose the **Embed** option. ![123FormBuilder Publish menu with the Embed option](/help/screenshots/00unszgu0p9np.png) 2. Copy any of the embed code options, **iframe**, **JS**, or **HTML**, all work the same way for attribution. ![123FormBuilder embed code options](/help/screenshots/d5tfj5notsu.png) 3. Paste the code into your website where you want the form to appear. With that, every form submission flows into SourceLoop with the visitor's first/last touch, UTMs, landing pages, and click IDs all attached automatically. > **Direct 123FormBuilder URLs aren't attributable** > Submissions made through a raw `.123formbuilder.com` URL **won't carry attribution data**. The visitor never lands on a page with the tracker, so there's no journey to attach. Route campaigns to a landing page on your own site that embeds the form. ## Where to see 123FormBuilder submissions in SourceLoop Once the integration is live, your form data shows up in three SourceLoop views: ### Contacts Hub Every 123FormBuilder submission is a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand a row to see the visitor's entire pre-submission journey: the channel they came from, the pages they read, and the sessions they took before finally filling the form. ![SourceLoop Contacts Hub showing a 123FormBuilder submission with the lead's full pre-submission journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Pull up [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to see your 123FormBuilder submissions grouped by source, medium, and campaign. Especially useful when you need to justify a marketing budget, the dashboard surfaces which channels convert and which only deliver clicks. ![SourceLoop attribution dashboard with 123FormBuilder submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in a "123FormBuilder submission" step. Slice by source or landing page to find your highest-converting acquisition paths. ![SourceLoop funnel report ending in a 123FormBuilder submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is part of the picture, forward your 123FormBuilder submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms optimize for completed forms instead of clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the offline-conversion setup. ## Frequently Asked Questions ### Does this work on the 123FormBuilder Free plan? The JS-script step in this guide uses 123FormBuilder's Advanced settings. Confirm with your 123FormBuilder plan whether the "Add a JS script to your form" option is included, this varies by tier. The tracking script on your website itself works on every plan. ### Why does 123FormBuilder need a helper script instead of just an embed? 123FormBuilder forms run inside an iframe that doesn't share submission events with the parent page out of the box. The small helper script you paste into 123FormBuilder bridges that gap so SourceLoop can capture submissions from your embed. ### What if I share my 123FormBuilder direct link instead of embedding? Submissions made through a raw `.123formbuilder.com` link won't carry attribution data because the visitor never lands on a page where the SourceLoop tracker is running. Embed the form on your own site for full attribution. ### Will the helper script affect form load speed or functionality? No. It's a small, asynchronously-loaded script that doesn't touch the form's UI or submission flow. Form completion times and conversion rates are unaffected. ### Can I use this with 123FormBuilder's Salesforce, HubSpot, or Zapier integrations? Yes. Your existing 123FormBuilder connectors continue to deliver submissions to their destinations. SourceLoop adds attribution data on top without conflicting with those flows. --- # How to track lead source in Formstack Capture the channel, campaign, and full journey behind every Formstack submission so you can credit the marketing efforts that produce real leads. Source: https://sourceloop.ai/help/track-lead-source-in-formstack/ Updated: 2026-05-28 --- Formstack is the form builder that businesses pick when they need enterprise capabilities, HIPAA-compliant configurations, payment integrations, conditional logic, audit trails, plus the polish of a SaaS-grade form tool. The blind spot remains the same as everywhere else: Formstack tells you what was submitted, but not which marketing investment drove the lead. This guide fills that in. Four steps, around ten minutes total. The webhook step requires Formstack on the Silver plan or above. ## What SourceLoop captures from Formstack Each Formstack submission lands in SourceLoop alongside: - **Acquisition channel** plus the visitor's complete UTM parameter set - **Pages visited** ahead of the form submission, in chronological order - **Cumulative time on site** before the submission - **Number of sessions** the respondent made before converting - **Email address** captured directly from the Formstack form - **First landing page** and the URL that referred them - **Source of the closing session**, the one that ended in the submission - **Device type, country, and browser** for segment analysis ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the Formstack form will be embedded - A **Formstack account on the Silver plan or above** (Free and Bronze plans don't include webhooks, which this guide requires) - At least one Formstack form built and published ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup** in the left sidebar, and click the **Tracking code** tab. Copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet into the `` of every page on your site. Site-wide installation gives SourceLoop the full picture of each visitor's journey before they reach the form. ## Step 2: Add the sl_aid hidden field to your Formstack form Formstack matches URL parameters against your form fields using the **field label**. Add a single hidden field labeled `sl_aid` so SourceLoop can identify each submission. 1. Open your form in the Formstack builder. 2. From the field palette, drag a **Short answer** field onto the form. ![Formstack form builder with the Short answer field being added](/help/screenshots/58knais83k8.png) 3. Set the **Label** to exactly: ``` sl_aid ``` Then check the **Hidden** checkbox. ![Formstack field configured with sl_aid label and Hidden checkbox enabled](/help/screenshots/5i7mamzticu.png) 4. Save and publish the form. > **Label must be lowercase with no spaces** > Formstack converts the label into the URL parameter name (spaces become underscores). If you label it `SL AID` or `Sl_Aid`, the value won't reach the form. Use `sl_aid` exactly. ## Step 3: Embed the form on your website The form needs to live on a page where the SourceLoop tracking script is also running. 1. Open your form and click the **Share** menu at the top. ![Formstack form builder with the Share menu open](/help/screenshots/mncucci6cvb.png) 2. Choose either **JavaScript** or **iframe** embed and copy the embed code. ![Formstack embed options showing JavaScript and iframe methods](/help/screenshots/nerewhnbgf.png) 3. Paste the embed code on your website where you want the form to appear. > **Direct Formstack URLs aren't attributable** > Submissions made through a raw `.formstack.com` link **won't carry attribution data**. The respondent never lands on a page with the tracker, so `sl_aid` never gets a value. Route campaigns through a landing page that embeds the form. ## Step 4: Configure the Formstack webhook The webhook delivers each Formstack submission to SourceLoop, where it's matched to the visitor's journey and saved as a fully-attributed lead. 1. In SourceLoop, go to **Setup -> Incoming Webhooks** and copy your webhook URL. ![SourceLoop Setup page on the Incoming Webhooks tab](/help/screenshots/a3xldt9w9fk.png) 2. In Formstack, open your form and go to **Settings -> Emails & Actions**. ![Formstack Settings page showing the Emails and Actions option](/help/screenshots/33mnaxogskt.png) 3. Click **Add Webhook**. ![Formstack Emails and Actions panel with Add Webhook button](/help/screenshots/lx42ents2e9.png) 4. Paste the SourceLoop webhook URL and click **Create**. ![Formstack webhook configuration with the SourceLoop URL pasted](/help/screenshots/ozzj1n43x9.png) 5. Submit a test form on your embedded page to confirm the submission appears in SourceLoop with source, channel, and landing page populated. ## Where to see Formstack submissions in SourceLoop Three SourceLoop views give you complementary perspectives on your Formstack data: ### Contacts Hub Every Formstack submission becomes a contact row in [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand any row to reveal the visitor's full pre-submission timeline, the channel that drove them, the content they browsed, and the sessions that led to the submit. ![SourceLoop Contacts Hub showing a Formstack submission with the lead's full pre-submission journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Pull up [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to see Formstack submissions grouped by source, medium, and campaign. The view answers "which channel is producing real leads?" at a glance. ![SourceLoop attribution dashboard with Formstack submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Formstack submission". Cut it by source, campaign, or landing page to find the most efficient paths from visit to submitted form. ![SourceLoop funnel report ending in a Formstack submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition matters to your team, forward Formstack submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms optimize for real lead capture instead of vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the offline-conversion setup. ## Frequently Asked Questions ### Does this require a paid Formstack plan? The hidden-field and embed steps work on every Formstack tier, but the webhook step requires the Silver plan or above. Free and Bronze plans don't include webhook integrations, so submissions can't reach SourceLoop without an upgrade. ### Formstack capitalizes my hidden field's first letter automatically. Will that break tracking? Formstack matches URL parameters using the field's label (spaces become underscores). Set the label to exactly `sl_aid`, lowercase, no spaces. If Formstack auto-capitalizes it on save, edit it back to lowercase. ### Do I need to add separate hidden fields for each UTM parameter? No. Just one field, `sl_aid`. SourceLoop fills in source, campaign, channel, landing page, and the rest on its side using that single identifier. ### My Formstack form is set to redirect after submission. Will tracking still work? Yes. The webhook fires from Formstack as soon as the submission is recorded, before any redirect. The thank-you page redirect on your form continues to work normally. ### I share my Formstack direct link in client emails. Are those submissions captured? No. Submissions made through a raw `.formstack.com` link won't carry attribution, because the visitor never lands on a tracked page first. Embed the form on a landing page that has the SourceLoop snippet for full attribution. --- # How to track lead source in Youform Capture the channel, campaign, and full visitor journey behind every Youform submission, no API keys, no custom code. Source: https://sourceloop.ai/help/track-lead-source-in-youform/ Updated: 2026-05-28 --- Youform is the minimal, design-led form builder that's gaining traction with indie founders, designers, and small teams who want something cleaner than the legacy form tools. Like every form tool, it tells you when a submission happens, not what brought the visitor in. This guide adds that context to every Youform submission. Four steps, roughly ten minutes start to finish. ## What SourceLoop captures from Youform Each Youform submission lands in SourceLoop with: - **Acquisition channel** plus the full UTM parameter set from the visitor's first session - **Browsing path** ahead of the submission, every page in order - **Total session time** on your site before the form fill - **Visit recurrence**: how many distinct sessions before they converted - **Email and name fields** from the Youform submission - **Original landing page** and the referring URL - **Source of the closing session**, the one that ended in the submit - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the form will be embedded - A **Youform account** with at least one form built and webhook integrations available on your plan ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, head to **Setup -> Tracking code** in the left sidebar, and copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet inside the `` of every page on your site. Especially the page where you'll embed Youform. ## Step 2: Add a hidden field to your Youform Youform supports hidden fields that populate from URL parameters. Add one named `sl_aid`. 1. Open your form inside Youform. 2. Go to the form's **Settings**. 3. Navigate to the **Hidden Fields & Variables** tab. 4. Add a new hidden field with the name: ``` sl_aid ``` 5. Click **Save**. That's the only hidden field required. SourceLoop matches submissions to the visitor's marketing journey on its side using this single identifier. ## Step 3: Embed your form on your website The form needs to live on a page where the SourceLoop tracker is loaded. 1. From your Youform **Share** page, make sure you've selected: - **Embed Type**: Inline Embed - **Type**: JS Embed 2. Copy the embed code provided by Youform. 3. Paste it into your website where you want the form to appear. > **Direct Youform links can't be attributed** > Submissions made through a raw Youform URL **won't carry attribution data**. The visitor never lands on a page with the tracker, so the hidden field stays empty. Embed the form on a landing page on your own site for full attribution. ## Step 4: Configure the webhook The webhook delivers each Youform submission to SourceLoop, where it gets matched to the visitor's journey and saved with full attribution. 1. In SourceLoop, go to **Setup -> Webhooks** and copy your webhook URL. 2. In Youform, open your form settings and add a webhook integration. Paste the SourceLoop URL. 3. Save. Submit a test response on your embedded form to confirm the submission arrives in SourceLoop with channel, source, and landing page populated. ## Where to see Youform submissions in SourceLoop Three SourceLoop views show your Youform data from different angles: ### Contacts Hub Every Youform submission is a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand any row to see the visitor's full pre-submission timeline, where they came from, what they browsed, and how long they took before submitting. ![SourceLoop Contacts Hub showing a Youform submission with the lead's full pre-submission journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls up your Youform submissions by source, medium, and campaign. Useful for spotting which acquisition efforts produce real leads. ![SourceLoop attribution dashboard with Youform submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Youform submission". Slice it by source, campaign, or landing page to find the paths that turn visitors into completed forms. ![SourceLoop funnel report ending in a Youform submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is in your mix, push the submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms optimize for filled forms instead of just clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through that setup. ## Frequently Asked Questions ### Does this work on the Youform Free plan? The hidden-field and embed parts are available on every plan, but the webhook step requires Youform's webhook integration, which may be limited on the free tier. Check Youform's plan comparison before starting if you're not sure. ### Why just one hidden field instead of separate ones for UTM source, medium, campaign, etc.? A single `sl_aid` identifier is all SourceLoop needs to look up the visitor's tracked journey on its side. You don't have to maintain seven hidden UTM fields, the attribution data is filled in server-side using that one value. ### I have an older Youform setup that uses "Include Parent Page URL Parameter". Should I keep that on? No, you can leave it off. The current SourceLoop integration doesn't rely on the parent-URL forwarding option, so that legacy step is no longer needed. ### What if I share my Youform direct link instead of embedding? Submissions made via the standalone Youform URL won't carry attribution data, because the visitor never visits a page that has the tracker. Always route campaigns through a landing page on your site that embeds the form. ### Can the webhook deliver more form fields than just sl_aid? Yes. Youform's webhook can include the rest of the form payload. SourceLoop only requires the `sl_aid` value to attribute the lead, but additional fields like email, name, and phone are useful to carry along for downstream tools. --- # How to track lead source in Zoho Forms Capture the marketing channel, campaign, and full pre-submission journey behind every Zoho Forms entry, with attribution flowing into each record. Source: https://sourceloop.ai/help/track-lead-source-in-zoho-forms/ Updated: 2026-05-28 --- Zoho Forms is the form builder of choice for businesses already running on the Zoho suite, especially when you need tight integration with Zoho CRM, Zoho Workplace, or any of Zoho's other tools. The catch most users hit: form entries flow into Zoho with no marketing context attached, no idea which ad, post, or campaign brought the visitor to the form. This guide adds the missing layer. Four steps, around fifteen minutes. The Field Alias step in step 2 is the one most people miss, watch for it. Webhook support requires a paid Zoho Forms plan. ## What SourceLoop captures from Zoho Forms Every Zoho Forms entry arrives in SourceLoop alongside: - **Acquisition channel** plus the visitor's complete UTM parameter set - **Browsing sequence** before the entry, every page in chronological order - **Total time on site** before the submission - **Session count** before they converted - **Email and name** from the Zoho Forms entry - **First-seen landing page** and the URL that referred the visitor - **Source of the closing session**, the one that produced the entry - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the Zoho Forms widget will be embedded - A **Zoho Forms account** on a paid plan (Basic or above for webhook support) - At least one Zoho Forms form built and published ## Step 1: Install the SourceLoop tracking script Log in to SourceLoop, open **Setup** from the left sidebar, and choose the **Tracking code** tab. Copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet to copy](/help/screenshots/sourceloop-tracking-code-script.png) Drop the snippet inside the `` of every page on your site, including the page where the Zoho Forms widget will live. ## Step 2: Add a hidden field with a Field Alias Zoho Forms uses **Field Alias** as the URL parameter name. You'll need both a hidden field on the form AND an alias set to `sl_aid` for the integration to work. 1. Open your form inside **Zoho Forms**. 2. Add a new field of type **Single Line**. Label it anything you like, "SourceLoop Id" works fine. ![Zoho Forms field added with a Single Line type](/help/screenshots/2kp01536agm.png) 3. Open the field's **Field Properties** panel. 4. Under **Field Visibility**, mark the field as **Hide** and save. ![Zoho Forms Field Visibility set to Hide](/help/screenshots/5vjfvzndhq9.png) 5. From the form builder's left sidebar, navigate to **Settings -> Field Alias - Prefill URL**. ![Zoho Forms Settings menu with Field Alias - Prefill URL highlighted](/help/screenshots/yhctc2d053.png) 6. Find your new field in the alias list and set its alias to exactly: ``` sl_aid ``` (lowercase, no spaces, aliases are case-sensitive). ![Zoho Forms Field Alias panel with sl_aid entered for the hidden field](/help/screenshots/twraouuna78.png) 7. Click **Save**. > **Don't skip the Field Alias step** > This is the step most teams miss. Without setting the alias, Zoho Forms ignores the `?sl_aid=...` URL parameter and the field stays empty on every submission. The hidden visibility alone isn't enough, the alias is what tells Zoho to populate the field from the URL. **Quick verify**: open your form's standalone URL with `?sl_aid=test123` appended (e.g. `https://forms.zohopublic.com/yourname/form/YourForm/formperma/HASH?sl_aid=test123`) and submit. The resulting entry should show the field populated with `test123`. If yes, the alias is wired correctly. ## Step 3: Embed the form on your website Once the form has the hidden field and alias, get it onto your site. 1. From your Zoho Forms **Share** tab, choose **Embed**. 2. Select **Iframe** or **JavaScript** embed (either works). 3. Copy the embed code and paste it onto your website where the form should appear. ![Zoho Forms Share tab showing Iframe and JavaScript embed options](/help/screenshots/x6v2jm1uxdj.png) > **Direct Zoho Forms URLs aren't attributable** > Entries made through a raw `forms.zohopublic.com//form/...` link **won't carry attribution data**. The visitor never lands on a page with the tracker. Always route campaigns through a landing page that embeds the form. ## Step 4: Configure the Zoho Forms webhook The webhook delivers each entry to SourceLoop, where it's matched to the visitor's journey and saved as an attributed lead. 1. In SourceLoop, go to **Setup -> Incoming Webhooks** and copy your webhook URL. ![SourceLoop Setup page on the Incoming Webhooks tab](/help/screenshots/7tj6tln9hvo.png) 2. In Zoho Forms, open your form, go to **Integrations -> Webhook**, and add a new webhook with the SourceLoop URL. ![Zoho Forms Integrations panel with Webhook configuration](/help/screenshots/r2nay7he56.png) 3. Set the content type to **application/json**. ![Zoho Forms webhook with content type set to application/json](/help/screenshots/rr9w1ltep2.png) 4. Configure the payload parameters. At minimum, send `name`, `email`, and `sl_aid`. Keep the parameter names exactly as shown below so SourceLoop can read them. ![Zoho Forms webhook payload parameters configured with name, email, and sl_aid](/help/screenshots/ro5d4pnv6o8.png) 5. Save. Submit a test form on your embedded page to confirm the entry appears in SourceLoop with source, channel, and landing page populated. ## Where to see Zoho Forms entries in SourceLoop Three SourceLoop views give different cuts of your Zoho Forms data: ### Contacts Hub Every Zoho Forms entry shows up as a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click a row to expand the visitor's full pre-submission timeline. ![SourceLoop Contacts Hub showing a Zoho Forms entry with the lead's full pre-submission journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups your Zoho Forms entries by source, medium, and campaign. Useful when you need to see which acquisition channels actually turn into qualified leads. ![SourceLoop attribution dashboard with Zoho Forms entries grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Zoho Forms entry". Slice by source or landing page to find the best paths from first visit to submitted form. ![SourceLoop funnel report ending in a Zoho Forms entry conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition, push Zoho Forms entries to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn from real lead generation. [Connect your Google Ads account](/help/connect-google-ads/) covers the offline-conversion setup. ## Frequently Asked Questions ### Why does the field need both a Hidden setting AND a Field Alias? Zoho Forms uses Hidden visibility to keep the field off the public form, but it uses Field Alias as the URL parameter name. Without a Field Alias set to `sl_aid`, Zoho ignores the incoming URL parameter and the field stays empty even though it's there. Both settings are required. ### I set the field as Hidden but skipped the Field Alias step. Why isn't tracking working? That's the most common mistake on this integration. Zoho requires the explicit alias to map URL parameters into fields. Add the alias `sl_aid` under Settings -> Field Alias - Prefill URL, save, and try again. ### Can I use the existing fields on my form instead of adding a new one? You can, but you'd need to set the Field Alias of one of your existing fields to `sl_aid`. Adding a dedicated hidden field is cleaner and won't affect your form's UI or data structure for visible fields. ### Do I need any Zoho plan in particular? Webhooks in Zoho Forms are available on paid plans (Basic and above). Free plans don't include webhooks, so submissions won't reach SourceLoop without an upgrade. The hidden-field and Field Alias steps work on any plan. ### I share my Zoho Forms direct link in client emails. Will those entries be tracked? No. Submissions through a raw `forms.zohopublic.com//form/...` link won't carry attribution because the visitor never lands on a tracked page first. Embed the form on a landing page that has the SourceLoop snippet. --- # How to track lead source in Fillout Bake the marketing channel, campaign, and full journey into every Fillout form submission, with attribution data living right next to the visible answers. Source: https://sourceloop.ai/help/track-lead-source-in-fillout/ Updated: 2026-05-28 --- Fillout has become a favorite of teams who want a powerful form builder with conditional logic, database lookups, payment integrations, and a clean visual editor. The piece it leaves to you is **attribution**: Fillout records what was submitted, but not what brought the visitor to the form. This guide bakes the marketing data right into the form submission. Four steps, about ten minutes. Works on every Fillout plan. ## What SourceLoop captures from Fillout After setup, each Fillout submission arrives with these hidden URL parameter values populated automatically: - **Channel** + **Latest Source**, **Medium**, **Campaign**, **Term**, **Content** (closing session's UTMs) - **Latest Landing Page** (where the closing session began) - Optional **First-touch** equivalents for multi-touch attribution - Plus the visitor's regular form answers (email, name, etc.) unchanged ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the Fillout form will be embedded - A **Fillout account** with at least one form built ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup -> Tracking code** in the left sidebar, and copy the snippet displayed. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet inside the `` of your site so every page (especially the one where you'll embed Fillout) loads the tracker. ## Step 2: Register URL Parameters in Fillout Fillout calls hidden fields **URL Parameters**. Each one must be registered in the form settings before its value can carry into the submission. **The repeatable workflow:** 1. Open your form in Fillout. 2. Go to the **Settings** page. ![Fillout form Settings page](/help/screenshots/c2yg01ip1yj.png) 3. Navigate to the **URL Parameters** section and click **Add New**. ![Fillout URL Parameters section with Add New button](/help/screenshots/aho878sr0a.png) 4. Set the **Name** to one of the parameters from the table below. ![Fillout new URL Parameter with a name entered](/help/screenshots/retk0inute.png) 5. Click **Save**. Repeat for each parameter you want to track. **Latest-touch parameters (recommended baseline):** | URL Parameter Name | What it carries | | --- | --- | | `channel` | High-level acquisition channel | | `attribution_source` | Latest session's UTM source | | `attribution_medium` | Latest session's UTM medium | | `attribution_campaign` | Latest session's UTM campaign | | `attribution_term` | Latest session's UTM term | | `attribution_content` | Latest session's UTM content | | `landingpage` | Page the closing session began on | > **Lowercase, no spaces, exact spelling** > Parameter names are case-sensitive and must match exactly. Use lowercase alphanumeric characters and underscores only. Fillout silently drops any parameter it hasn't seen registered. ## Step 3: Track first-touch attribution (optional) Skip this step unless you want multi-touch attribution, the ability to compare the original acquisition channel against the most recent one. To track first-touch, register these additional parameters following the same workflow as step 2: | URL Parameter Name | | --- | | `first_channel` | | `first_source` | | `first_medium` | | `first_campaign` | | `first_term` | | `first_content` | | `first_landingpage` | | `firstseen` | With both touch sets in place, every Fillout submission carries the complete picture, the channel that originally found the prospect and the channel that finally converted them. ## Step 4: Embed the Fillout form on your website The form needs to live on a page where the SourceLoop tracking script is also running. 1. In Fillout, open your form and click **Share**. ![Fillout form with the Share button](/help/screenshots/r6ilmipgz4.png) 2. Choose **Standard** (inline iframe) as the embed type. ![Fillout Share dialog with Standard embed selected](/help/screenshots/y9raotnnaqe.png) 3. Copy the embed code and paste it on your website where the form should appear. ![Fillout embed code ready to copy](/help/screenshots/h3h96zs2adk.png) Submit a test form and check the results page in Fillout, the URL parameter values you registered should appear alongside the visible field answers. ![Fillout results page showing attribution URL parameter values on a submission](/help/screenshots/1b6r2d0rdxy.png) > **Direct Fillout URLs aren't attributable** > Submissions made through a raw `forms.fillout.com/` link **won't carry attribution data**. The visitor never lands on a page with the tracker, so the URL parameters arrive empty. Route campaigns through a landing page that embeds the form. ## Where to see Fillout submissions in SourceLoop Beyond the attribution data living inside each Fillout submission, three SourceLoop views give you complementary cuts: ### Contacts Hub Every Fillout submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand any row to see the visitor's full pre-submission timeline. ![SourceLoop Contacts Hub showing a Fillout submission with the lead's full pre-submission journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups your Fillout submissions by source, medium, and campaign for a high-level read on which channels produce leads. ![SourceLoop attribution dashboard with Fillout submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Fillout submission". Slice by source or landing page to find your top-converting acquisition paths. ![SourceLoop funnel report ending in a Fillout submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid teams, push Fillout submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn from real lead generation. [Connect your Google Ads account](/help/connect-google-ads/) covers that setup. ## Frequently Asked Questions ### Why does Fillout call hidden fields "URL Parameters"? Fillout's terminology, the same concept other form tools call hidden fields. Each URL parameter you register acts as a hidden field that gets populated from the corresponding URL query string. Same effect, different name. ### Do I have to register every parameter? That looks like a lot. No. Start with the latest-touch set in step 2, that's the baseline view. The click-ID parameters are only useful if you run paid ads. The first-touch parameters in step 3 are only needed if you want multi-touch attribution. Register what you'll actually use. ### What if I miss-spell a URL parameter name? Fillout silently ignores any URL parameter that hasn't been registered. The value reaches the page but never makes it into the form data. Always double-check the spelling against the names in this guide. ### I share my Fillout direct URL on social media. Do those submissions get attribution? No. Submissions through a raw `forms.fillout.com/` URL won't carry attribution because the visitor never visits a page that has the SourceLoop tracker. Embed Fillout on your own site to capture the data. ### Will SourceLoop's hidden parameters interfere with my form's logic or scoring rules? No. The parameters live as hidden values in the submission and don't affect Fillout's logic blocks, scoring, or branching. They flow through to your submission data as ordinary fields. --- # How to track lead source in GoHighLevel Forms Bake the marketing channel, campaign, and journey into every GoHighLevel form submission so each contact arrives with full attribution attached. Source: https://sourceloop.ai/help/track-lead-source-in-gohighlevel-form/ Updated: 2026-05-28 --- GoHighLevel is the operating system for marketing agencies and one-person consultancies, lead capture, CRM, automation, calendar, all in one place. The piece it doesn't solve out of the box is attribution: a form submission lands in your contact record with the basic fields filled in, but no marketing context about where the lead came from. This guide adds that context as hidden fields, right inside the contact record. Four steps, around fifteen minutes. Works on every GoHighLevel plan since hidden fields are a standard form-builder feature. ## What SourceLoop captures from GoHighLevel Forms Each form submission lands in your GoHighLevel contact record with these hidden field values populated automatically: - **SourceLoop Id** (the link to the visitor's full tracked journey) - **Channel** + **Latest Source**, **Medium**, **Campaign** (closing session's UTM details) - Optional **First-touch** equivalents for multi-touch attribution - Plus the visitor's regular form fields (name, email, phone, etc.) unchanged ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website (or GoHighLevel funnel page) where the form will be embedded - A **GoHighLevel sub-account** with at least one form built - About fifteen minutes to add hidden fields one at a time ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup** in the left sidebar, and click the **Tracking code** tab. Copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet inside the `` of every page on your site that will host a GoHighLevel form. If your forms are embedded on GoHighLevel funnel pages, you can install the snippet in your funnel's tracking settings instead. ## Step 2: Add hidden fields to your form Each attribution value needs a custom field on your GoHighLevel form, so the data flows into the contact record (and any downstream automations or webhooks you've configured). **The repeatable workflow for each field:** 1. In your form, click **+ Add Element -> Custom Fields -> Add Custom Field**. 2. Set **Field Type** to **Single-line** and the **Custom Field Name** as shown in the table. 3. Set the **Unique Key** (sometimes labeled Query Key) to the exact value shown. 4. In the field's right-panel properties, tick the **Hidden** checkbox. 5. Save the form. > **Unique Keys lock after saving** > Once saved, GoHighLevel will not let you change a field's Unique Key. Mistyping means deleting the field and creating a new one. Take a second to verify the exact spelling against the table below. **Required field:** | Field Name | Field Type | Unique Key | Hidden | | --- | --- | --- | --- | | SourceLoop Id | Single-line | `sl_aid` | yes | **Latest-touch fields (recommended baseline):** | Field Name | Field Type | Unique Key | Hidden | | --- | --- | --- | --- | | Channel | Single-line | `channel` | yes | | Latest Source | Single-line | `attribution_source` | yes | | Latest Medium | Single-line | `attribution_medium` | yes | | Latest Campaign | Single-line | `attribution_campaign` | yes | ## Step 3: Track first-touch attribution (optional) Skip this step unless you want multi-touch attribution. To track the original acquisition channel separately from the most recent one, add these additional fields using the same workflow as step 2: | Field Name | Field Type | Unique Key | Hidden | | --- | --- | --- | --- | | First Channel | Single-line | `first_channel` | yes | | First Source | Single-line | `first_source` | yes | | First Medium | Single-line | `first_medium` | yes | | First Campaign | Single-line | `first_campaign` | yes | | First Landing Page | Single-line | `first_landingpage` | yes | ## Step 4: Embed the form on your website The form needs to live on a page that has the SourceLoop tracking script loaded. 1. In GoHighLevel, navigate to **Sites -> Forms -> Integrate Form** and copy the embed code. 2. Paste it into your website (or GoHighLevel funnel page) where you want the form to appear. Submit a test form, then open the resulting contact in GoHighLevel. The SourceLoop Id, Channel, Latest Source, and other hidden fields should all have values populated. > **Direct GoHighLevel form links can't be attributed** > If you share the form's raw `link.msgsndr.com/...` URL directly in SMS blasts, emails, or DMs, those submissions **won't carry attribution data**. The visitor never lands on a page with the tracker, so the hidden fields stay empty. Route those campaigns through a landing page that embeds the form. ## Where to see GoHighLevel submissions in SourceLoop Beyond the attribution data flowing directly into your GoHighLevel contact records, three SourceLoop views give different cuts of your form data: ### Contacts Hub Each GoHighLevel form submission appears as a contact row in [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand a row to see the visitor's complete pre-submission journey. ![SourceLoop Contacts Hub showing a GoHighLevel form submission with the lead's full pre-submission journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls up GoHighLevel form submissions by source, campaign, and landing page. Especially valuable for agencies managing paid spend across multiple clients. ![SourceLoop attribution dashboard with GoHighLevel form submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "GoHighLevel form submission". Slice by source or landing page to find which acquisition paths produce real leads. ![SourceLoop funnel report ending in a GoHighLevel form submission conversion step](/help/screenshots/sourceloop-funnel.png) For agencies running paid acquisition for clients, push the submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn from real form fills. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Why are the Unique Keys lowercase and underscored? GoHighLevel matches URL parameters against the field's Unique Key (sometimes called Query Key). It's case-sensitive and must match exactly. Lowercase with underscores is the convention SourceLoop uses across every tool that needs this kind of mapping. ### The Unique Key is locked after I save the field. Can I change it later? No. Once saved, GoHighLevel locks the Unique Key permanently. If you mistype it, you'll need to delete the field and create a new one with the correct key. Double-check before saving. ### Do these hidden fields show up to my prospects? No. Each field has the Hidden checkbox enabled, so visitors never see them. They're filled in from the page URL when the form loads, and they flow through to your GoHighLevel contact record alongside the visible answers. ### I run multiple sub-accounts for clients. Do I need to set this up for each? Yes. Each GoHighLevel sub-account is its own form environment, so the hidden fields need to be added inside each client's account where their forms live. Each client website also needs its own SourceLoop workspace and tracking script. ### Can I use this with GoHighLevel funnels that contain forms? Yes. As long as the funnel page hosting the form has the SourceLoop tracking script installed, the hidden fields populate the same way they do on standard pages. --- # How to track lead source in involve.me Capture the marketing channel, campaign, and full visitor journey behind every involve.me funnel submission as hidden field values. Source: https://sourceloop.ai/help/track-lead-source-in-involve-me/ Updated: 2026-05-28 --- involve.me is the interactive funnel tool that lead-gen teams reach for when a flat form just isn't enough, quizzes, calculators, multi-step lead capture, embedded payment screens. Powerful for engagement, but the funnel won't tell you which marketing channel drove the prospect through. This guide adds that context to every funnel submission. Four steps, around fifteen minutes total. Requires involve.me on the Grow or Scale plan for Hidden Fields support. ## What SourceLoop captures from involve.me After setup, each involve.me funnel submission carries these hidden field values automatically: - **SourceLoop Id** (the link to the visitor's full tracked journey) - **Channel** + **Latest Source**, **Medium**, **Campaign**, **Term**, **Content** (closing session's UTMs) - Optional **First-touch** equivalents for multi-touch attribution - Plus the funnel's regular answers, scoring results, and conditional outputs unchanged ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the involve.me funnel will be embedded - An **involve.me account on the Grow or Scale plan** (Free and Start don't include Hidden Fields) - At least one funnel built in involve.me ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, head to **Setup -> Tracking code** in the left sidebar, and copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet inside the `` of your site, especially on the page where the involve.me funnel will live. ## Step 2: Register URL Parameters in involve.me involve.me captures URL parameter values into **Hidden Fields** only when those fields have been registered in the funnel editor. Each one needs to be added explicitly. **The repeatable workflow:** 1. Open your funnel in the involve.me editor. 2. Click **Settings -> Hidden Fields** (sometimes labeled **URL Parameters**, depending on UI version). 3. For each parameter in the tables below, click **Add Field**, enter the **Parameter name** exactly as shown, leave the default value empty, and save. > **Names are case-sensitive** > Parameter names must be lowercase, alphanumeric, and underscores only. involve.me silently drops any URL parameter that hasn't been registered. Spell each one exactly as listed. **Required:** | Parameter Name | | --- | | `sl_aid` | **Latest-touch parameters (recommended baseline):** | Parameter Name | | --- | | `channel` | | `attribution_source` | | `attribution_medium` | | `attribution_campaign` | | `attribution_term` | | `attribution_content` | ## Step 3: Track first-touch attribution (optional) Skip this step unless you want multi-touch attribution. To track the original acquisition channel separately from the most recent one, register these additional parameters using the same workflow as step 2: | Parameter Name | | --- | | `first_channel` | | `first_source` | | `first_medium` | | `first_campaign` | | `first_term` | | `first_content` | | `first_landingpage` | ## Step 4: Embed your involve.me funnel on your website The funnel needs to live on a page where the SourceLoop tracking script is also loaded. involve.me has to be embedded through its own embed snippet, not linked to externally. 1. In involve.me, open your funnel and click **Share & Embed**. 2. Choose **Inline / Embedded** (Popup, Slider, and Side Tab all work too). 3. Copy the embed code. It looks like: ```html
``` 4. Paste it on your website where the funnel should appear. Submit a test entry, then open it in involve.me's Submissions panel, the hidden field values (`sl_aid`, `channel`, etc.) should appear in the entry data. > **Direct involve.me funnel links can't be attributed** > Submissions made through a raw involve.me funnel URL **won't carry attribution data**. The visitor never lands on a page with the tracker, so the hidden fields arrive empty. Always route campaigns through a landing page that embeds the funnel. ## Where to see involve.me submissions in SourceLoop Beyond the attribution data flowing into each funnel submission, three SourceLoop views give complementary cuts: ### Contacts Hub Each involve.me submission shows up as a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand a row to reveal the visitor's complete pre-funnel journey. ![SourceLoop Contacts Hub showing an involve.me submission with the lead's full pre-funnel journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls up your involve.me submissions by source, medium, and campaign. Useful for spotting which channels drive engagement-quality leads. ![SourceLoop attribution dashboard with involve.me submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "involve.me submission". Cut it by source or landing page to find your most productive acquisition paths. ![SourceLoop funnel report ending in an involve.me submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is in your mix, forward involve.me submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms can learn from real lead capture. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Why do I need to register each parameter individually? involve.me only saves URL parameter values into Hidden Fields that have been pre-registered in the funnel editor. Any parameter name it hasn't seen is dropped silently. Register every name you want to capture, exactly as listed. ### Do I need a paid involve.me plan? Yes for this workflow. Hidden Fields require the Grow or Scale plan on involve.me. Free and Start tiers don't support capturing hidden field data through URL parameters. ### I have a funnel with multiple pages and conditional logic. Does the attribution data survive the journey? Yes. Once the hidden fields are populated at the funnel entry point, they persist through every page, branch, and conditional path inside the funnel. The final submission carries the full set. ### What if I share my involve.me funnel via its direct URL on social media? Submissions through a raw involve.me funnel URL won't carry attribution because the visitor never lands on a page where the SourceLoop tracker is running. Always embed the funnel on a landing page that has the tracker installed. ### Can I add my own hidden fields alongside the SourceLoop ones? Absolutely. The SourceLoop parameters live alongside whatever hidden fields you already use. Just keep the SourceLoop parameter names spelled exactly as the guide shows so they get matched correctly. --- # How to track lead source in Webflow Forms Tie every Webflow form submission back to the campaign, content, or channel that actually drove it, with the visitor's pre-submission journey attached. Source: https://sourceloop.ai/help/track-lead-source-in-webflow/ Updated: 2026-05-28 --- Webflow is the visual-development platform of choice for design-led teams, freelancers, and agencies who want the freedom of custom design without writing layout code. The blind spot in most Webflow setups: form submissions arrive in your inbox or CRM with no marketing context attached. This guide closes that gap. Three steps, about five minutes, no Webflow API or custom code required. ## What SourceLoop captures from Webflow Forms Every Webflow form submission lands in SourceLoop alongside: - **Acquisition channel** plus the full UTM parameter set from the visitor's first session - **Browsing trail** ahead of the form fill, every page in order - **Total time on site** before they submitted - **Visit count** before the conversion happened - **Email and name** captured from the Webflow form fields - **Original landing page** and the URL that referred them - **Source of the closing session** (the one that ended in the submit) - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to your Webflow project's Custom Code settings (or your custom domain's HTML) - At least one Webflow form set up on a page ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup -> Tracking code** in the left sidebar, and copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) In Webflow, head to **Project Settings -> Custom Code -> Head Code** and paste the snippet there. Webflow injects it into every page on the site automatically. Publish your site to push the change live. ## Step 2: Confirm your form is on a published page Once the SourceLoop tracking script is live on your Webflow site, every Webflow form on every page is ready to be tracked. No per-form configuration needed. A few things worth checking: - The form is on a **published** Webflow page (drafts won't show the snippet) - The form **collects an email** (SourceLoop uses email to create the lead record) - Your **Form Settings -> Form Submission** action is configured as you normally would (success message, redirect, etc.). SourceLoop runs alongside, doesn't replace it > **External Webflow form URLs aren't trackable** > If a visitor lands directly on a hosted Webflow form URL outside your main site, attribution can't be captured because there's no tracked session leading into it. Always send campaigns to your main domain pages that host the form. ## Step 3: Verify it's working Open your form page in an **incognito window**, add `?utm_source=test&utm_medium=verify&utm_campaign=webflow-check` to the URL, and submit a test entry using a real email you control. Within seconds, the submission should appear on the **Contacts Hub** in SourceLoop with the test UTM values populated. ## Where to see Webflow submissions in SourceLoop ### Contacts Hub Each Webflow form submission becomes a row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand a row to reveal the visitor's complete pre-submission journey, the campaign that brought them in, the pages they browsed, and how many sessions they took before submitting. ![SourceLoop Contacts Hub showing a Webflow form submission with the lead's full pre-submission journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls up your Webflow submissions by source, medium, and campaign. Useful when you need to compare paid acquisition to organic and content channels at a glance. ![SourceLoop attribution dashboard with Webflow submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Webflow submission". Slice by source, campaign, or landing page to find your highest-converting acquisition paths. ![SourceLoop funnel report ending in a Webflow form submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is part of the picture, forward your Webflow submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms optimize for real form completions instead of clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through that wiring. ## Frequently Asked Questions ### Does this work for Webflow CMS forms and Logic-Jump forms too? Yes. Whether it's a standard form, a CMS-bound form, or a multi-step Logic form, the attribution flow is the same. Each submission on a SourceLoop-tracked page is captured automatically. ### Will SourceLoop interfere with Webflow's built-in form submission flow? No. Webflow's confirmation message, redirect, and built-in spam filtering all continue to work. SourceLoop runs separately and attaches attribution data without touching the form's native behavior. ### Can I use this with Webflow's Mailchimp, HubSpot, or Zapier integrations? Yes. Your existing integrations continue to deliver submissions to their connected destinations. SourceLoop adds the attribution layer in parallel without conflicting. ### I host my Webflow form on a non-Webflow domain (custom domain, subdomain, etc). Does that work? Yes, as long as the SourceLoop tracking script is installed on whatever domain hosts the form. Webflow's domain settings don't affect SourceLoop's ability to capture submissions. ### What about Webflow forms shown in popups or modals? Popups and modals work the same way for attribution. As long as the parent page has the SourceLoop snippet, any form inside a popup will get captured on submit. --- # How to track lead source in WPForms Add real source attribution to every WPForms submission so your WordPress site reveals which channel actually drives leads, not just clicks. Source: https://sourceloop.ai/help/track-lead-source-in-wpforms/ Updated: 2026-05-28 --- WPForms is the most-installed form plugin on WordPress, the default choice for blogs, agencies, ecommerce sites, and small businesses. Its strength is simplicity, drag and drop, no code. The catch most users hit later: when leads start coming in, there's no built-in way to see which marketing effort produced them. This guide fixes that. Three-step setup, around five minutes. Works on every WordPress site, every WPForms plan. ## What SourceLoop captures from WPForms After setup, each WPForms submission arrives in SourceLoop with: - **Acquisition source** plus the visitor's full UTM parameter set - **Pre-submission browsing path**, ordered chronologically - **Time on site** before the form was completed - **Number of return visits** before the conversion - **Email and name** captured from the WPForms fields - **Landing page** and referring URL from the first session - **Last session source**, the one that produced the submission - **Device type, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Admin access** to your WordPress site (or a way to edit `` markup, such as a Tag Manager) - At least one form built in WPForms and embedded on a page ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, head to **Setup -> Tracking code** in the left sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add the snippet to the `` section of your WordPress site. Three common ways to do this: - **Header / Footer plugin**: install a plugin like "Insert Headers and Footers" and paste into the Header field - **Theme editor**: paste into your active theme's `header.php` just before the closing `` tag - **Site Kit / SEO plugin**: many SEO plugins include a "Custom Code" section in their site settings Whichever method, the script must load on every page that hosts a WPForms form. ## Step 2: Confirm the form is on a tracked page Once the tracking script is loading site-wide, every WPForms form is ready to be tracked, no per-form configuration required. Check that: - Your form is **embedded on a published WordPress page** (not just sitting in the Forms list) - Your form **collects an email field** (SourceLoop uses email to create the lead) - Any caching plugin (WP Rocket, W3 Total Cache, etc.) isn't deferring or async-ing the SourceLoop script in a way that breaks load order > **Forms hosted on disconnected pages aren't trackable** > If you use WPForms' "View Form" link to host the form on a standalone page outside your main site, those submissions can't be attributed. Always embed the form on a page within your main site to capture the source. ## Step 3: Verify it's working Open your form page in an **incognito window**, add `?utm_source=test&utm_medium=verify&utm_campaign=wpforms-check` to the URL, and submit a test entry using a real email you control. Within a few seconds, the submission should appear on the **Contacts Hub** in SourceLoop with the test UTMs attached. ## Where to see WPForms submissions in SourceLoop ### Contacts Hub Every WPForms submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand any row to see the visitor's complete pre-submission journey. ![SourceLoop Contacts Hub showing a WPForms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Pull up [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to see WPForms submissions broken down by source, medium, and campaign. ![SourceLoop attribution dashboard with WPForms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), configure a funnel ending in "WPForms submission". Cut it by source or landing page to spot your best paths from visit to submitted form. ![SourceLoop funnel report ending in a WPForms submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is part of your strategy, forward WPForms submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real lead generation. [Connect your Google Ads account](/help/connect-google-ads/) covers the setup. ## Frequently Asked Questions ### Does this work on the free WPForms Lite plugin? Yes. SourceLoop's tracking happens on the page in your browser, not via the WPForms API, so it works on every WPForms tier including the free Lite version. ### Will this work with WPForms' built-in Stripe, Mailchimp, or HubSpot addons? Yes. Your existing WPForms addons continue to handle submissions to their connected destinations. SourceLoop adds attribution on top without touching those flows. ### I use the WPForms multi-page or conversational form layouts. Are those captured? Yes. SourceLoop captures the final submit event regardless of how many pages or steps the form has. The conversational layout works the same way. ### My WordPress site uses caching plugins (WP Rocket, W3 Total Cache, etc.). Will the tracking script still work? Yes, as long as the script is placed in the `` (or your theme's header.php) before any caching plugin minifies or defers it. Some caching plugins have options to exclude specific scripts from defer/async, use that if you encounter timing issues. ### What happens if a visitor submits a WPForms form via AJAX vs page reload? Both work. SourceLoop captures the submission event in either case, so you don't need to change your form's submission handling. --- # How to track lead source in Gravity Forms Capture which marketing channel drove every Gravity Forms submission, with the complete visitor journey saved alongside each lead. Source: https://sourceloop.ai/help/track-lead-source-in-gravity-forms/ Updated: 2026-05-28 --- Gravity Forms is the premium form plugin many WordPress agencies and serious site owners build on, deep customization, robust add-ons, enterprise-grade reliability. The blind spot remains the same as every form tool: it can tell you what was submitted, but not which marketing channel earned the submission. This guide fills that in. Three steps, around five minutes, no Gravity Forms API calls required. ## What SourceLoop captures from Gravity Forms Each Gravity Forms submission arrives in SourceLoop with: - **Acquisition channel** plus the full UTM parameter set - **Page-by-page browsing path** ahead of the submission - **Cumulative session time** on your site before the form fill - **Number of separate visits** before the conversion - **Email and name** from the Gravity Forms fields - **First-touch landing page** and the URL that referred them - **Source of the converting session**, often distinct from first-touch - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Admin access** to your WordPress site - A **Gravity Forms license** (any tier) with at least one form built and embedded on a page ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup -> Tracking code** in the left sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add the snippet to the `` of your WordPress site. The simplest path is a header injection plugin like "Insert Headers and Footers", but pasting into your theme's `header.php` works equally well. If you use a tag manager, drop it in as a Custom HTML tag set to fire on all pages. ## Step 2: Confirm your form is on a published page Once the tracking script is live, every Gravity Forms form on every published page is ready to be tracked. No per-form configuration needed. A few sanity checks: - The form is embedded on a **published** WordPress page (drafts don't carry the snippet) - The form **collects an email field** (SourceLoop uses email to create the lead) - Your form's confirmation message or redirect continues to work as expected > **Standalone Gravity Forms URLs aren't trackable** > Sharing the Gravity Forms preview URL or any standalone form link outside your main site bypasses the tracker, those submissions arrive without attribution. Always route visitors to a published page on your site that embeds the form. ## Step 3: Verify it's working Open the form page in an **incognito tab**, add `?utm_source=test&utm_medium=verify&utm_campaign=gravity-check` to the URL, and submit a test entry using a real email you control. Within seconds, the submission should appear on the **Contacts Hub** in SourceLoop with the test UTMs attached. ## Where to see Gravity Forms submissions in SourceLoop ### Contacts Hub Every Gravity Forms submission appears as a row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand a row for the visitor's complete pre-submission journey. ![SourceLoop Contacts Hub showing a Gravity Forms submission with the full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups your Gravity Forms submissions by source, medium, and campaign. ![SourceLoop attribution dashboard with Gravity Forms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Gravity Forms submission". Slice by source or landing page to find your best-converting paths. ![SourceLoop funnel report ending in a Gravity Forms submission conversion step](/help/screenshots/sourceloop-funnel.png) For teams running paid acquisition, push Gravity Forms submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn from real form completions. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Does this work with Gravity Forms' Logic, conditional fields, and multi-page forms? Yes. The submission is the only event SourceLoop cares about, the complexity of the form itself doesn't change anything. Logic Jumps, multi-page flows, and conditional fields all work normally. ### I use Gravity Forms add-ons like Stripe, PayPal, or Mailchimp. Will they still fire? Yes. Your existing Gravity Forms add-ons continue to handle their connected workflows. SourceLoop runs alongside without touching them, just adding attribution data on top. ### My WordPress site uses a caching layer (Cloudflare, NGINX, etc.). Will tracking still work? Yes, as long as the SourceLoop snippet loads in the page `` before the form. Page-level caching of HTML doesn't affect the tracker because the script loads dynamically per session. ### Can I track Gravity Forms submitted via AJAX (Enable AJAX option)? Yes. Whether the form submits via standard POST or AJAX, the submission event is captured the same way. ### Does this work with Gravity Forms embedded inside an Elementor or Divi page? Yes. The page builder is irrelevant, as long as your WordPress site has the SourceLoop tracking script in the global ``, any embedded Gravity Form gets tracked. --- # How to track lead source in Tally Tie every Tally form submission back to the marketing channel that drove it, complete with the visitor's pre-submission journey. Source: https://sourceloop.ai/help/track-lead-source-in-tally/ Updated: 2026-05-28 --- Tally has quietly become the form-builder that minimalists, indie founders, and Notion power-users gravitate toward, free, clean, and Notion-ish in feel. The missing piece is the same one every form tool ducks: which marketing channel drove each submission. This guide adds that context to every Tally form fill. Three steps, around five minutes start to finish. ## What SourceLoop captures from Tally Each Tally submission lands in SourceLoop alongside: - **Acquisition source** with the full UTM parameter set - **Browsing sequence** before the form was submitted - **Time invested on your site** before the submission - **Visit count** before the prospect finally submitted - **Email and name** captured from Tally's fields - **Original landing page** and the referring URL - **Source of the converting session** - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where you'll embed the Tally form - A **Tally account** with at least one form published ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, head to **Setup -> Tracking code** in the left sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet inside the `` of your site. Site-wide install is best, especially the page where you'll embed Tally. ## Step 2: Embed your Tally form on a tracked page In Tally, open your form and go to **Share -> Embed**. Tally offers a few placement options: - **Inline embed**: drops the form directly onto your page - **Popup**: opens the form as a modal - **Slider**: slides in from the side - **Full-page**: takes over the whole viewport Pick what fits your design. Copy the embed code Tally generates and paste it on your site where the form should appear. The page must also have the SourceLoop snippet from step 1. > **Direct tally.so URLs aren't attributable** > Submissions made through a raw `tally.so/r/` link **won't carry attribution data**. The visitor never lands on a tracked page, so there's nothing for SourceLoop to attribute. Always route campaigns to a landing page that embeds the form. ## Step 3: Verify it's working Open the page with your Tally form in an **incognito window**, add `?utm_source=test&utm_medium=verify&utm_campaign=tally-check` to the URL, and submit a test entry. Within a few seconds, the submission should appear on the **Contacts Hub** in SourceLoop with the test UTM values populated on the contact record. ## Where to see Tally submissions in SourceLoop ### Contacts Hub Every Tally submission is a row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click into a row to see the visitor's complete pre-submission timeline. ![SourceLoop Contacts Hub showing a Tally submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls up Tally submissions by source, medium, and campaign for a quick read on what's working. ![SourceLoop attribution dashboard with Tally submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Tally submission" and slice by source or landing page to identify your highest-converting paths. ![SourceLoop funnel report ending in a Tally submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition, forward Tally submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn from real form fills. [Connect your Google Ads account](/help/connect-google-ads/) covers that setup. ## Frequently Asked Questions ### Does this work on Tally's Free plan? Yes. Tally is generous with what its free plan includes, and SourceLoop attaches attribution to submissions made through any Tally form, regardless of which Tally plan you're on. ### Will tracking work on Tally's popup, slider, and full-page embed modes? Yes. Whichever embed mode you use, inline, popup, slider, or full-page, the submission event is captured the same way as long as the embed loads on a tracked page. ### I share my Tally form via tally.so/r/... link in newsletters. Will those submissions be attributed? No. Submissions through Tally's direct tally.so URL won't carry attribution because the visitor never lands on a tracked page first. Embed the form on a landing page that has SourceLoop installed. ### Tally has a "Notion-style" workflow with calculations and logic. Does any of that affect tracking? No. The calculations, conditional logic, and dynamic fields inside Tally all run independently of SourceLoop. The submission event fires at the end the same way regardless of form complexity. ### Can I use this with Tally's Notion, Airtable, or Slack integrations? Yes. Tally's connectors continue to deliver responses to their destinations. SourceLoop runs alongside without conflict. --- # How to track lead source in Mailchimp for WordPress See exactly which channel, content, or campaign drives every signup through your Mailchimp for WordPress (MC4WP) forms. Source: https://sourceloop.ai/help/track-lead-source-in-mailchimp-for-wp/ Updated: 2026-05-28 --- Mailchimp for WordPress is the plugin most WordPress sites use to grow their Mailchimp list, lightweight, well-supported, and free. Where it leaves you hanging is the same place every form tool does: you see signups, but not where the signups came from. This guide brings the marketing channel context to every MC4WP submission. Three steps, about five minutes. ## What SourceLoop captures from MC4WP Each MC4WP signup arrives in SourceLoop with: - **Acquisition channel** plus the full UTM parameter set - **Page sequence** visited before the signup - **Time on site** before subscribing - **Number of visits** before they converted - **Email and name** from the MC4WP form - **Original landing page** and the URL that referred them - **Last-touch source** before the signup - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Admin access** to your WordPress site - The **Mailchimp for WordPress (MC4WP)** plugin installed with at least one form configured ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, head to **Setup -> Tracking code** in the left sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to your WordPress site's ``. Easiest options: - A header-injection plugin (e.g. "Insert Headers and Footers") - Your theme's `header.php` template - A Google Tag Manager Custom HTML tag Make sure the script loads on every page where an MC4WP form lives. ## Step 2: Confirm the MC4WP form is on a tracked page No per-form configuration is needed. Once the tracking script is live site-wide, every MC4WP form is ready to be tracked. Verify that: - The form is **embedded on a published page** via shortcode, Gutenberg block, or widget - The form **collects an email field** (MC4WP requires this by default) - Your caching plugin isn't deferring SourceLoop's snippet in a way that prevents it from loading before the form > **Pages outside your main site aren't trackable** > If you somehow expose the MC4WP signup outside your WordPress site (a Mailchimp-hosted page, for example), SourceLoop can't see those signups. They live entirely on Mailchimp's domain with no SourceLoop tracker present. ## Step 3: Verify it's working Open your MC4WP form's page in an **incognito tab**, add `?utm_source=test&utm_medium=verify&utm_campaign=mc4wp-check` to the URL, and complete a test signup. Within seconds, the new contact should appear on the **Contacts Hub** in SourceLoop with the test UTMs attached. ## Where to see MC4WP signups in SourceLoop ### Contacts Hub Each MC4WP signup is a row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand for the full pre-signup journey. ![SourceLoop Contacts Hub showing a Mailchimp for WP signup with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups your signups by source, medium, and campaign. Helpful for understanding which content actually grows your list. ![SourceLoop attribution dashboard with MC4WP signups grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Mailchimp for WP signup". Slice by source to find what drives newsletter subscriptions vs. what drives traffic that doesn't convert. ![SourceLoop funnel report ending in an MC4WP signup conversion step](/help/screenshots/sourceloop-funnel.png) For teams running paid acquisition aimed at list growth, forward MC4WP signups to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms can optimize against real list adds. [Connect your Google Ads account](/help/connect-google-ads/) covers the setup. ## Frequently Asked Questions ### Does this work with the free MC4WP plugin or only MC4WP Premium? Both. SourceLoop attaches attribution to the visitor's session and captures the form submission regardless of whether you're on free or premium MC4WP. ### My MC4WP form is shown via a shortcode in a page or post. Is that tracked? Yes. SourceLoop captures submissions from MC4WP forms inserted via shortcode, Gutenberg block, or widget. Whatever placement method you use, the submission is picked up as long as the host page has the tracking script. ### What happens to my MC4WP confirmation message or redirect? Nothing changes. SourceLoop runs separately and doesn't interfere with MC4WP's success message, redirect URL, or list subscription logic. ### I have multiple MC4WP forms on the same page. Are all of them tracked? Yes. Each form's submission is captured separately as its own conversion. The email submitted dictates which contact in SourceLoop the conversion is attached to. ### Does the visitor's data also flow into Mailchimp? Yes, exactly as it normally does. MC4WP's existing Mailchimp sync is untouched. SourceLoop adds attribution data on its side without changing what reaches Mailchimp. --- # How to track lead source in ConvertKit (Kit) Find out which channel, podcast, or piece of content actually drove every ConvertKit signup, with the full pre-signup journey saved on each subscriber. Source: https://sourceloop.ai/help/track-lead-source-in-convertkit/ Updated: 2026-05-28 --- ConvertKit (rebranded as Kit) is the email tool creators, podcasters, course-sellers, and indie newsletter writers reach for first. The catch: it tells you who subscribed but rarely where they came from. This guide brings that context to every Kit signup, so you can credit the podcast guest spot, the LinkedIn post, or the ad that actually drove a new subscriber. Three-step setup, around five minutes. ## What SourceLoop captures from ConvertKit / Kit Each ConvertKit signup lands in SourceLoop with: - **Acquisition channel** plus the visitor's full UTM parameter set - **Pre-signup browsing trail** in chronological order - **Time on site** before subscribing - **Number of return visits** before the signup - **Email** captured from the ConvertKit form (Name and other fields are also forwarded if present) - **Original landing page** and the URL that referred them - **Source of the converting session**, useful when first-touch and last-touch differ - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the ConvertKit form will be embedded - A **ConvertKit (Kit) account** with at least one form built under **Grow -> Landing Pages & Forms** ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, head to **Setup -> Tracking code** in the left sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet inside the `` of your site so it loads on every page that will host a ConvertKit form. ## Step 2: Embed your ConvertKit form In ConvertKit (Kit), navigate to **Grow -> Landing Pages & Forms** and either pick an existing form or create a new one. A few notes: - Choose either the **Inline** or **Modal/Slide-in** embed type for full SourceLoop compatibility - Click **Embed** and copy the JavaScript or HTML snippet - Paste the snippet into your site where you want the form to appear (or in your site's `` for floating/modal embeds) - Confirm the form includes an **Email** field, this is what SourceLoop uses to identify the lead > **ConvertKit Landing Pages aren't trackable** > ConvertKit's hosted landing pages (at `pages.convertkit.com`) live outside your domain, so the SourceLoop tracker doesn't load there. Signups via hosted landing pages won't carry attribution. Always embed forms on your own site for full source data. ## Step 3: Verify it's working Open your site in an **incognito window**, navigate to (or trigger) your ConvertKit form, enter a name and email, and submit. Within seconds, the new subscriber should appear on the **Contacts Hub** in SourceLoop as a Web Form conversion with the submitted email and the visitor's attribution data attached. ## Where to see ConvertKit signups in SourceLoop ### Contacts Hub Each ConvertKit signup becomes a row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand the row to see the visitor's pre-signup journey, including the originating channel and every page they viewed before subscribing. ![SourceLoop Contacts Hub showing a ConvertKit signup with the lead's full pre-signup journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups your ConvertKit signups by source, medium, and campaign. Useful for creators who want to know "did that podcast appearance actually drive subscribers?" ![SourceLoop attribution dashboard with ConvertKit signups grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "ConvertKit signup". Slice by source to find your most subscriber-friendly traffic sources. ![SourceLoop funnel report ending in a ConvertKit signup conversion step](/help/screenshots/sourceloop-funnel.png) If you run paid ads to grow your list, push ConvertKit signups to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn to optimize for real subscribers rather than just clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Does this work on the ConvertKit Free plan? Yes. The integration uses the standard ConvertKit form embed, which is available on every plan including Free. ### Will this work with ConvertKit's Landing Pages too, or only embedded forms? SourceLoop captures form submissions on your own site that has the tracking script. If you use a ConvertKit Landing Page (hosted at pages.convertkit.com), that page isn't on your domain and the tracker isn't there, so those signups won't be attributed. Embed forms on your own site for full attribution. ### I'm using ConvertKit's modal/popup form. Does the popup count as a tracked page? Yes, as long as the parent page hosting the popup has the SourceLoop snippet. The popup itself doesn't need any extra setup. ### Will SourceLoop interfere with ConvertKit's sequences, automations, or tagging rules? No. The subscription itself reaches ConvertKit exactly as it would otherwise, triggering every sequence and tag rule you've set up. SourceLoop adds attribution data on its side without touching the ConvertKit-side flow. ### Does this work after the ConvertKit rebrand to "Kit"? Yes. ConvertKit and Kit are the same product, the embed code and integration behavior haven't changed with the rebrand. --- # How to track lead source in Klaviyo Forms Find out which channel drove every Klaviyo signup, whether it came from a popup, flyout, or embedded form, with the visitor's pre-signup journey attached. Source: https://sourceloop.ai/help/track-lead-source-in-klaviyo/ Updated: 2026-05-28 --- Klaviyo is the email and SMS engine behind most modern ecommerce stores, the default choice for Shopify, BigCommerce, and Magento brands. Klaviyo tells you who subscribed and what they bought, but the marketing channel that originally drove each signup is usually a black box. This guide makes it visible. Three-step setup, about five minutes, no API tokens required. ## What SourceLoop captures from Klaviyo Each Klaviyo signup arrives in SourceLoop with: - **Acquisition source** plus the full UTM parameter set from the visitor's first session - **Pre-signup browsing path**, including product pages viewed - **Time on site** before the signup - **Visit count** leading to the conversion - **Email and/or phone** captured by the Klaviyo form - **Original landing page** and the URL that referred them - **Source of the closing session**, often distinct from the first-touch source - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to your site's `` (Shopify theme, WordPress header, etc.) - A **Klaviyo account** with at least one signup form built under **Audience -> Sign-up forms** - Klaviyo's **onsite snippet** loaded on your site (Klaviyo installs this automatically on Shopify; otherwise paste their JS into ``) ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, head to **Setup -> Tracking code** in the left sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add the snippet to the `` of your site. On Shopify, paste it into your theme's `theme.liquid` file just before ``. On WordPress, use a header-injection plugin or your theme's `header.php`. The SourceLoop script and Klaviyo's onsite snippet can both live in the same `` without conflict. ## Step 2: Configure your Klaviyo signup form In Klaviyo, navigate to **Audience -> Sign-up forms** and either pick an existing form or create a new one. A few requirements for attribution to work: - Form includes an **Email** field (Klaviyo's default), or a **SMS/Phone** field, or both - Form is **published** so it loads on your live site - Klaviyo's **onsite snippet is active** on your site (check Klaviyo's setup page if you're not sure) Once published, the form is ready, no additional configuration on the SourceLoop side is needed. > **Optional name and custom fields are forwarded too** > You can keep using whatever extra fields your Klaviyo form collects (name, birthday, store preference). SourceLoop forwards whatever the visitor submits alongside the required email or phone. ## Step 3: Verify it's working Open your site in an **incognito window**. Scroll until the popup appears (or trigger your embedded form), enter a name and email, and submit. Within seconds, the conversion should appear on the **Contacts Hub** in SourceLoop with the submitted email and the visitor's attribution data populated. ## Where to see Klaviyo signups in SourceLoop ### Contacts Hub Each Klaviyo signup is a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand a row to see the full pre-signup journey: which product pages the visitor browsed, what brought them to the site, and how many sessions they took before subscribing. ![SourceLoop Contacts Hub showing a Klaviyo signup with the lead's full pre-signup journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Klaviyo signups by source, medium, and campaign. Particularly useful for ecommerce, see exactly which channel drives list growth vs. just product page traffic. ![SourceLoop attribution dashboard with Klaviyo signups grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "Klaviyo signup". Slice by source or landing page to find which acquisition paths actually grow your list. ![SourceLoop funnel report ending in a Klaviyo signup conversion step](/help/screenshots/sourceloop-funnel.png) For paid ecommerce campaigns, push Klaviyo signups to **Google Ads, Meta, and TikTok as offline conversions** so the bidding algorithms can train on the visitors who actually become subscribers, not just clickers. [Connect your Google Ads account](/help/connect-google-ads/) covers that setup. ## Frequently Asked Questions ### Does this work with every Klaviyo signup form type (popup, flyout, embed)? Yes. SourceLoop captures submissions from any Klaviyo signup form rendered on a tracked page, whether it's a popup triggered by exit intent, a flyout, an inline embed, or a multi-step form. ### My Klaviyo form captures SMS / phone instead of email. Does SourceLoop still track it? Yes. SourceLoop creates a lead as long as either email or SMS/phone is captured. If your form collects only name or another custom field with no contact identifier, there's nothing to follow up with and no lead is created. ### Will this work for ecommerce sites running Klaviyo on Shopify? Yes. The setup is identical, the SourceLoop tracking script goes in the Shopify theme's ``, and Klaviyo's onsite snippet stays where it normally is. Both run side by side without conflict. ### Does SourceLoop change anything about how Klaviyo handles the signup? No. Klaviyo continues to add the subscriber to its lists, fire flows, apply tags, and trigger automations exactly as configured. SourceLoop only adds attribution data on its own side. ### I share a direct link to a Klaviyo form. Will those submissions be captured? Klaviyo forms only render on pages where the Klaviyo onsite snippet is loaded, which is your own site. Submissions on your site's pages are captured; there's no separate "direct link" scenario for Klaviyo forms the way other tools have. --- # How to track lead source in Framer Forms Capture the channel, campaign, and full visitor journey behind every Framer form submission with a single hidden field. Source: https://sourceloop.ai/help/track-lead-source-in-framer/ Updated: 2026-05-28 --- Framer is the design-led website builder picked by product teams, freelancers, and design studios who want pixel-perfect control without writing layout code. Its native Form component is good but, like every form tool, blind to marketing source. This guide fixes that with a single hidden field setup. Three quick steps, around five minutes, no API access required. ## What SourceLoop captures from Framer Forms Each Framer form submission lands in SourceLoop with: - **Acquisition channel** plus the full UTM parameter set - **Page sequence** the visitor took before submitting - **Time on site** before the form was filled - **Number of visits** before they converted - **Email and name** from the Framer form fields - **First landing page** and the URL that referred them - **Last-session source**, the one that produced the submission - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to your Framer project's Site Settings (specifically the Custom Code section) - At least one Framer page with a Form component ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, head to **Setup -> Tracking code** in the left sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) In Framer, open your project and go to **Site Settings -> Custom Code -> Head**. Paste the SourceLoop snippet there. Republish your site to push the change live. ## Step 2: Add a hidden field to your Framer form Open the page that has your form and select an existing Form Field component (or add a new one). In the field's properties panel, set: ``` Name = sl_aid Type = Hidden ``` Save your form. That's the only field you need to add. SourceLoop fills in the channel, source, medium, campaign, landing page, and other attribution details on its side using this one identifier. > **Framer-hosted preview URLs aren't trackable** > Sharing a Framer preview link (the `.framer.app` URL) doesn't trigger SourceLoop's tracker because the snippet is added to your published custom-domain site, not preview environments. Always send campaigns to your live published URL. ## Step 3: Verify it's working Open your form page on the live site in an **incognito window**, add `?utm_source=test&utm_medium=verify&utm_campaign=framer-check` to the URL, and submit a test entry using a real email. Within seconds, the submission should appear on the **Contacts Hub** in SourceLoop with the test UTMs attached to the contact. ## Where to see Framer submissions in SourceLoop ### Contacts Hub Each Framer submission becomes a row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand a row for the visitor's full pre-submission journey. ![SourceLoop Contacts Hub showing a Framer submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Framer submissions by source, medium, and campaign for a high-level view of what's working. ![SourceLoop attribution dashboard with Framer submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Framer submission". Slice by source or landing page to find your most-converting paths. ![SourceLoop funnel report ending in a Framer submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition, forward your Framer submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn from real form completions. [Connect your Google Ads account](/help/connect-google-ads/) walks through the wiring. ## Frequently Asked Questions ### Why just one hidden field? The single `sl_aid` identifier is everything SourceLoop needs to look up the full attribution data, channel, source, medium, campaign, landing page, click IDs, first-touch and last-touch, server-side. You don't have to maintain a dozen hidden fields per form. ### Where does the tracking script go in Framer? Inside your Framer project's Site Settings -> Custom Code -> Head section. It loads on every page of your published Framer site automatically. ### Does this work for inline Framer Forms and modal/popup forms? Yes. Both work the same way. The Hidden field setup is a property on the form field, regardless of how the form is displayed on the page. ### What if I migrated from an older SourceLoop setup that required many hidden fields? You can delete all the older fields and keep just `sl_aid`. The newer setup hydrates the remaining attribution data on SourceLoop's side rather than requiring it on the form. ### Will the hidden field be visible to my form respondents? No. Setting the field's Type to "Hidden" in Framer hides it from the rendered form. Respondents see only the visible fields you've added (name, email, etc.). --- # How to track lead source in Contact Form 7 Attribute every Contact Form 7 submission to the campaign or channel that produced it, with the visitor's pre-submission journey attached. Source: https://sourceloop.ai/help/track-lead-source-in-contact-form-7/ Updated: 2026-05-28 --- Contact Form 7 is the WordPress plugin that quietly powers more contact forms than any other tool, simple, free, ubiquitous. The drawback most CF7 users accept is having no idea where the leads come from. This guide adds that visibility. Three steps, around five minutes. ## What SourceLoop captures from Contact Form 7 Each CF7 submission lands in SourceLoop with the visitor's acquisition channel, full UTM set, browsing path, time on site, visit count, email, original landing page, last-touch source, and device/country/browser. Same depth as the bigger form tools, just on top of CF7's lightweight plugin. ## Before you start - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Admin access** to your WordPress site - **Contact Form 7** installed with at least one form embedded on a page ## Step 1: Install the SourceLoop tracking script From SourceLoop's **Setup -> Tracking code** tab, copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Drop it into your WordPress site's ``, easiest via a "Headers and Footers" plugin or directly in your theme's `header.php`. ## Step 2: Confirm your CF7 form is on a tracked page Once the script is live, every Contact Form 7 form is ready. Confirm the form is on a published page, includes an email field, and that no caching plugin is delaying SourceLoop's snippet past the form load. > **Forms hosted off your domain aren't trackable** > If you somehow display a CF7 form on a page outside your main WordPress site, the SourceLoop tracker won't be there to capture it. Always embed on a published page within your main site. ## Step 3: Verify it's working Open the form page in an **incognito tab**, add `?utm_source=test&utm_medium=verify&utm_campaign=cf7-check`, and submit a test entry. The submission should appear in **Contacts Hub** within seconds. ## Where to see Contact Form 7 submissions ### Contacts Hub Submissions appear at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with the full pre-submission journey. ![SourceLoop Contacts Hub showing a Contact Form 7 submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls CF7 submissions up by source, medium, and campaign. ![SourceLoop attribution dashboard with Contact Form 7 submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Contact Form 7 submission" to compare paths by conversion rate. ![SourceLoop funnel report ending in a Contact Form 7 submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition, push submissions to Google Ads, Meta, and LinkedIn as offline conversions via [Connect your Google Ads account](/help/connect-google-ads/). ## Frequently Asked Questions ### Does this work with Contact Form 7 free, or is there a paid add-on involved? Yes, with the free CF7 plugin. Nothing paid is required, the integration works at the website level using the standard SourceLoop tracking script. ### Will this conflict with the Akismet, reCAPTCHA, or Flamingo add-ons I run alongside CF7? No. Those add-ons continue handling their roles (spam filtering, captcha, submission archiving) without interference from SourceLoop. ### My CF7 form uses AJAX submission. Does that matter for tracking? No. SourceLoop captures the submission whether CF7 reloads the page or processes the submission via AJAX. ### I have multiple CF7 forms on one page (newsletter + contact). Are both tracked separately? Yes. Each form's submission creates its own conversion attached to the submitted email. Visitors who submit multiple forms appear as the same contact. --- # How to track lead source in Elementor Forms Capture which marketing channel drove every Elementor form submission, with the visitor's full journey saved next to the lead. Source: https://sourceloop.ai/help/track-lead-source-in-elementor/ Updated: 2026-05-28 --- Elementor is the WordPress page-builder most marketing teams default to: drag-and-drop, conversion-focused, with a built-in Form widget on the Pro plan. The blind spot is the same one every form tool ducks: which channel produced each submission. This guide brings that context to every Elementor form fill. ## What SourceLoop captures from Elementor Forms Every Elementor form submission arrives in SourceLoop with the visitor's acquisition channel, full UTM set, browsing path, session count, time on site, email + name, original landing page, last-session source, and device/country/browser context. ## Before you start - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Admin access** to your WordPress site - **Elementor Pro** with at least one Form widget on a published page ## Step 1: Install the SourceLoop tracking script From SourceLoop's **Setup -> Tracking code** tab, copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to your WordPress site's ``. Easiest: a header-injection plugin, or paste into your theme's `header.php`. ## Step 2: Confirm the Elementor form is on a published page Once the script loads site-wide, every Elementor Form widget is ready. Verify the form is on a published Elementor page (not a draft), collects an email field, and your caching plugin isn't deferring the SourceLoop snippet past the form's load. > **Standalone Elementor preview URLs aren't trackable** > Elementor's preview links and template previews don't carry the SourceLoop tracker. Always route campaigns to published pages on your main domain. ## Step 3: Verify it's working Open your Elementor form page in incognito with `?utm_source=test&utm_medium=verify&utm_campaign=elementor-check`, submit a test entry, then check **Contacts Hub** in SourceLoop. ## Where to see Elementor submissions ### Contacts Hub Submissions show up at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with the full pre-submission journey. ![SourceLoop Contacts Hub showing an Elementor form submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls up your Elementor submissions by source, medium, and campaign. ![SourceLoop attribution dashboard with Elementor submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Configure a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Elementor submission" to compare conversion rates by source. ![SourceLoop funnel report ending in an Elementor submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition, push Elementor submissions to ad networks as offline conversions via [Connect your Google Ads account](/help/connect-google-ads/). ## Frequently Asked Questions ### Does this require Elementor Pro? The Form widget is an Elementor Pro feature. The SourceLoop tracking script itself works on any Elementor site, but you need Pro to have a form to track. ### Will Elementor's actions like email, MailChimp, ActiveCampaign continue to fire? Yes. Every Elementor Form action you've configured continues to fire normally. SourceLoop runs alongside without disturbing them. ### I run Elementor as part of a larger WordPress theme. Does that matter? No. As long as the SourceLoop snippet is in the site's ``, the theme is irrelevant to attribution capture. ### My Elementor form is inside a popup created via Elementor Popups. Does that work? Yes. As long as the parent page hosting the popup has the SourceLoop snippet, submissions made through the popup are captured. --- # How to track lead source in Ninja Forms Pair every Ninja Forms submission with the marketing channel that produced it, plus the visitor's full browsing path before they filled out the form. Source: https://sourceloop.ai/help/track-lead-source-in-ninja-forms/ Updated: 2026-05-28 --- Ninja Forms is a long-running staple of the WordPress form-plugin space, free, extensible, and popular with developers who like add-on driven setups. Where it leaves a gap, like every form builder, is on the marketing side: you see every entry but never the channel that earned it. This guide closes that gap. Three steps, around five minutes, no Ninja Forms-side configuration required. ## What SourceLoop captures from Ninja Forms Once installed, every Ninja Forms submission shows up in SourceLoop with: - **Source and medium** of the visitor's session, plus their UTM parameters - **Full page-by-page path** taken before they submitted - **Total time on site** leading up to the form fill - **Number of prior visits** before this conversion - **Email and name** lifted from the Ninja Forms fields - **First-touch landing page** along with the original referrer - **Source attributed to the converting session** (often different from first-touch) - **Device, country, and browser** of the submitter ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Admin access** to your WordPress site, or the ability to add code to `` - A **Ninja Forms** form embedded on a published WordPress page ## Step 1: Install the SourceLoop tracking script In SourceLoop, head over to **Setup -> Tracking code** in the left sidebar and copy the snippet provided. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) On WordPress, you have a few good options for placing it in ``: - A header-injection plugin like "Insert Headers and Footers" (one paste, done) - Your active theme's `header.php`, just before `` - The "Custom Code" panel that most SEO plugins (Yoast, Rank Math, SEOPress) expose - A tag manager set to fire on all pages Whichever path you choose, the script needs to load on every page that hosts a Ninja Forms form. ## Step 2: Confirm your form is on a tracked page No per-form switches to flip. Once the snippet is loading site-wide, every Ninja Form on every published page is automatically eligible for tracking. A handful of quick checks: - The form is embedded on a **published** page or post, not a draft - The form **includes an email field**, SourceLoop uses email to identify the lead - Aggressive caching or script-defer rules aren't reordering SourceLoop after the form loads > **Standalone Ninja Forms preview URLs aren't attributable** > If you share the Ninja Forms preview URL or any link that points outside your main site, those submissions arrive without attribution context. Route campaigns to a published page on your site that contains the embedded form instead. ## Step 3: Verify it's working Open the page hosting your Ninja Form in an **incognito tab**, append `?utm_source=test&utm_medium=verify&utm_campaign=ninja-check` to the URL, and submit a real test entry using an email address you can check. Within seconds, the submission should land on the **Contacts Hub** in SourceLoop with the test UTM values stamped on the record. ## Where to see Ninja Forms submissions in SourceLoop ### Contacts Hub Every Ninja Forms submission lands as a row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click into any row to see the visitor's entire pre-submission timeline, every page, every prior session, every campaign touchpoint. ![SourceLoop Contacts Hub showing a Ninja Forms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Open [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to roll up Ninja Forms submissions by source, medium, and campaign. Quick read on which channels are pulling their weight. ![SourceLoop attribution dashboard with Ninja Forms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel that ends in "Ninja Forms submission". Slice it by source, landing page, or device to find which paths reliably convert. ![SourceLoop funnel report ending in a Ninja Forms submission conversion step](/help/screenshots/sourceloop-funnel.png) Running paid? Pipe Ninja Forms submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the ad platforms optimise toward real form completions, not just clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Does this work on the free Ninja Forms plugin or do I need a paid add-on? It works on the free plugin. SourceLoop's tracking is browser-side, completely independent of Ninja Forms' add-on architecture, so every tier is supported, free Lite included. ### Will my Ninja Forms add-ons (Mailchimp, Constant Contact, Salesforce, Webhooks, etc.) keep working? Yes. SourceLoop sits next to your existing add-ons, not in front of them. Submissions still flow to every connected destination, and SourceLoop layers attribution on top. ### Ninja Forms submits via AJAX by default. Does that affect tracking? No. AJAX submissions and traditional page-reload submissions are both captured the same way. ### I use multi-part Ninja Forms with conditional logic. Anything different? Nothing changes. SourceLoop only cares about the final submit, the number of steps or conditional branches in between is irrelevant. ### Does this work with the Ninja Forms Layout & Styles add-on or custom form templates? Yes. Visual customisations don't affect the submission event, so attribution gets attached regardless of how the form is styled. --- # How to track lead source in Fluent Forms Stop guessing which campaign drove your Fluent Forms entries. Stamp every submission with its true source, UTMs, and the visitor's path on the way in. Source: https://sourceloop.ai/help/track-lead-source-in-fluent-forms/ Updated: 2026-05-28 --- Fluent Forms is the WordPress form plugin built around speed and developer ergonomics, lightweight, fast in the editor, and packed with integrations once you scale up to Pro. What it can't tell you is the same blind spot every form has: which marketing channel actually delivered each lead. This guide closes the loop. Three quick steps, around five minutes, and zero Fluent Forms configuration needed. ## What SourceLoop captures from Fluent Forms After setup, here's what's attached to every Fluent Forms entry inside SourceLoop: - **First-touch source** of how the visitor originally found you - **UTM parameters** for source, medium, campaign, content, term - **Sequence of pages** the visitor browsed before submitting - **Time elapsed on your site** prior to the submission - **Repeat visits** that preceded this conversion - **Email + name** read straight from your Fluent Forms fields - **Landing page** that opened the journey - **Source of the converting session** (the one that finally produced the form fill) - **Device, country, and browser** of the lead ## Before you start You'll need: - A **SourceLoop account** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** rights, or another way to inject markup into `` - A **Fluent Forms** form (Lite or Pro) embedded on a live page ## Step 1: Drop the SourceLoop snippet into your site Inside SourceLoop, go to **Setup -> Tracking code** in the left sidebar and grab the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) On WordPress, the cleanest options are: - A dedicated header plugin (e.g., "Insert Headers and Footers", "WPCode") - Your active theme's `header.php` immediately before `` - A "Custom Code" / "Header Scripts" panel inside an SEO plugin - Google Tag Manager firing on All Pages The script needs to load site-wide, definitely on every page where a Fluent Forms form lives. ## Step 2: Make sure the form is embedded on a tracked page There's nothing to configure inside Fluent Forms itself. Once the snippet is live across your site, every Fluent Forms entry becomes attributable automatically. Verify the basics: - The form is on a **published** page or post (drafts won't carry the snippet for anonymous visitors) - The form **collects an email**, SourceLoop uses email as the lead identifier - Defer/async rules in your performance plugin aren't shoving SourceLoop below the form > **Direct-link previews can't be attributed** > Sharing a Fluent Forms preview URL or any form link that lives outside your normal site means the visitor never lands on a tracked page. Those submissions show up in Fluent Forms but won't have a marketing source. Always send traffic to a normal page that embeds the form. ## Step 3: Send a test submission Open the form's page in an **incognito window** with `?utm_source=test&utm_medium=verify&utm_campaign=fluent-check` glued to the URL. Submit a real entry using an inbox you can access. Inside a few seconds, that submission should appear on the **Contacts Hub** in SourceLoop with the test UTM values visible on the record. ## Where to see Fluent Forms submissions in SourceLoop ### Contacts Hub The single source of truth for every lead: [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Each Fluent Forms entry becomes a row; expand it for the lead's entire pre-submission journey, ordered by timestamp. ![SourceLoop Contacts Hub showing a Fluent Forms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Open [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) for a campaign-level read. Fluent Forms submissions get sliced by source, medium, and campaign so you can see which efforts are pulling their weight at a glance. ![SourceLoop attribution dashboard with Fluent Forms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Want to know which path turns the most visitors into form fills? Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) with "Fluent Forms submission" as the final step, then slice by source, content, or device. ![SourceLoop funnel report ending in a Fluent Forms submission conversion step](/help/screenshots/sourceloop-funnel.png) If you spend on Google Ads, Meta, or LinkedIn, forward Fluent Forms submissions back as **offline conversions** so the platforms can optimise toward lead generation instead of clicks. The [Connect your Google Ads account](/help/connect-google-ads/) walkthrough covers the wiring. ## Frequently Asked Questions ### Does SourceLoop work with Fluent Forms Lite (the free version)? Yes. Tracking is wired up entirely in the browser, with zero reliance on Fluent Forms' database or Pro-only modules, so Lite users are fully supported. ### I use Fluent Forms' Conversational Form layout. Does that still get attributed? Yes. Whichever layout you ship, classic, step-based, or conversational, the final submission carries the attribution data attached to that session. ### Will my existing Fluent Forms integrations (FluentCRM, Mailchimp, ActiveCampaign, Slack) still fire? Yes. SourceLoop doesn't replace or proxy any of your existing destinations. Fluent Forms keeps sending data to your CRM, ESP, or Slack channel as configured; SourceLoop just enriches the lead record on its side. ### Fluent Forms supports conditional fields and calculation logic. Do those interfere with tracking? No. Conditional logic, calculations, and field-level rules all run inside Fluent Forms itself. SourceLoop only watches for the submission event, which fires once the visitor reaches the end. ### I'm running Fluent Forms on a WooCommerce checkout or login flow. Will SourceLoop attribute those too? Yes, as long as the page hosting the form has the SourceLoop snippet loaded in its ``. The form's location (homepage, contact, checkout, gated content) is irrelevant. --- # How to track lead source in Formidable Forms Wire your Formidable Forms submissions into a complete attribution picture: source, campaign, journey, and device attached to every lead. Source: https://sourceloop.ai/help/track-lead-source-in-formidable-forms/ Updated: 2026-05-28 --- Formidable Forms is the WordPress form plugin that punches above its weight, calculations, conditional logic, repeating fields, views, and full-blown app-builder territory once you hit Pro. The one job it doesn't claim to do is marketing attribution. That's what SourceLoop fills in. Three steps, a few minutes of setup, and every Formidable submission afterwards carries its acquisition story. ## What SourceLoop captures from Formidable Forms Each submission lands in SourceLoop with this context wrapped around it: - **Original acquisition channel** (organic, paid, social, referral, direct, etc.) - **Full UTM stack**: source, medium, campaign, content, term - **Pages visited** in order, ahead of the form fill - **Time on site** across all pre-submission sessions - **Repeat-visit count** before the conversion - **Email and full name** read from the Formidable fields - **First landing page** of the visitor's history with you - **Source of the converting session** (often a different referrer than first-touch) - **Device, location, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or any way to edit `` markup) - A **Formidable Forms** form, Lite or Pro, embedded on a published page ## Step 1: Add SourceLoop's tracking snippet to your site Open SourceLoop, click into **Setup -> Tracking code** in the left sidebar, and copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Drop it inside `` of your WordPress site. Common ways: - A header/footer plugin (e.g., "Insert Headers and Footers", "WPCode") - `header.php` of your active theme, just before the closing `` - The "Custom Code" section many SEO plugins expose - Google Tag Manager configured to fire on All Pages The script must load on every page that hosts a Formidable form, easiest if you simply install it site-wide. ## Step 2: Check the form is on a live, tracked page Formidable doesn't need any per-form changes. Once the snippet is live, every Formidable Form on every page that includes the snippet is tracked. Quick sanity pass: - The form is **embedded on a published page or post** (drafts aren't accessible to anonymous visitors) - The form **collects an email address**, SourceLoop uses email as the lead ID - Your caching/optimisation plugin isn't deferring SourceLoop past the form's submit > **Formidable preview / direct entry URLs aren't trackable** > Sharing a Formidable preview link or a direct entry URL that lives outside your normal site means the visitor never lands on a SourceLoop-tracked page. Those submissions exist in Formidable but have no marketing source. Always route campaigns through a page on your site that contains the embedded form. ## Step 3: Submit a test entry to confirm Pop open the form's page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=formidable-check` glued onto the URL. Submit a real entry using an email you can check. Inside a few seconds, you should see the lead appear at the top of the **Contacts Hub** in SourceLoop, with all three test UTM values stamped on the contact. ## Where to see Formidable Forms submissions in SourceLoop ### Contacts Hub Head to [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) for the per-lead view. Every Formidable submission becomes a contact row, and clicking through reveals the visitor's complete browsing timeline up to the submission. ![SourceLoop Contacts Hub showing a Formidable Forms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the bird's-eye view, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls Formidable submissions up by source, medium, campaign, and landing page so you can compare channels at a glance. ![SourceLoop attribution dashboard with Formidable Forms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) with "Formidable Forms submission" as the goal step. Cut it by source, content, or country to see which paths convert and which leak. ![SourceLoop funnel report ending in a Formidable Forms submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is in your mix, send Formidable submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the auction algorithms learn from real lead generation and not vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Will this work with Formidable Forms Lite? Yes. SourceLoop's tracking is browser-side and tool-agnostic, so it works equally well with the free Lite plugin and the Pro tier (and every add-on bundle in between). ### I use Formidable Views to display submitted entries. Does that change anything? No. Views render existing entries, they don't create them. SourceLoop fires when a new submission is made, regardless of what you do with the entry afterwards. ### Are my Formidable Forms calculations, repeater fields, and conditional logic preserved? Yes. Everything you've built inside Formidable Forms runs untouched. SourceLoop only attaches attribution metadata, it doesn't manipulate the form or its fields. ### I use Formidable for application-style forms with multiple sections. Will every completion be tracked? Yes. Whether it's a five-field contact form or a 50-field multi-section application, each completed submission is what triggers the capture, and the visitor's pre-submission browsing is attached as context. ### Can I use this on a membership site with Formidable forms behind a paywall? Yes, provided the visitor's pre-paywall sessions were tracked. SourceLoop stitches anonymous browsing to the eventual submission once an email is captured, even if part of the journey happened before they were logged in. --- # How to track lead source in Forminator Forms Add real campaign attribution to every Forminator form, quiz, poll, or payment submission, with the visitor's entire pre-conversion journey attached. Source: https://sourceloop.ai/help/track-lead-source-in-forminator-forms/ Updated: 2026-05-28 --- Forminator is the multi-tool of WordPress form plugins, contact forms, quizzes, polls, calculators, and payment forms in one package from WPMU DEV. The thing it doesn't cover is what most marketers eventually need to know: which channel drove each submission. That's where SourceLoop comes in. Three steps, only minutes to wire up, and you'll never have to ask "where did that lead come from?" again. ## What SourceLoop captures from Forminator For every Forminator submission, SourceLoop attaches: - **Acquisition channel** the visitor arrived through - **All UTM parameters** carried in the landing URL - **Sequence of pages browsed** during the visit - **Time spent on your site** across all pre-conversion sessions - **Count of repeat visits** before they finally converted - **Email and name** captured from the Forminator fields - **Original landing page** that started the journey - **Source of the submitting session**, recorded separately from first-touch - **Device, browser, and country** of the visitor ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access or another way to edit `` HTML - A **Forminator form, quiz, poll, or payment form** embedded on a published page ## Step 1: Drop the SourceLoop snippet into WordPress Sign in to SourceLoop, click **Setup -> Tracking code** in the left sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) On WordPress, the easiest paths are: - The WPMU DEV "Hummingbird" / "Smush" stack often has a header-inject panel - A standalone plugin like "Insert Headers and Footers" or "WPCode" - Your active theme's `header.php` immediately before `` - Tag Manager firing on All Pages Make sure the snippet loads site-wide, anywhere a Forminator form, quiz, or poll might appear. ## Step 2: Confirm your form, quiz, or poll lives on a tracked page There's no per-form switch in Forminator to flip. The moment your site has SourceLoop in ``, every embedded Forminator element becomes attributable. Make sure: - The element is on a **published** page or post (not a draft or private page) - The form, quiz, or poll **collects an email** at some point in the flow - Aggressive performance plugins aren't reshuffling SourceLoop after Forminator's own scripts > **Forminator preview links can't carry attribution** > Sharing a preview URL or a standalone hosted form link outside your normal site means the visitor never sees a tracked page, so the submission shows up in Forminator with no source. Always route campaigns to a published page that embeds the form. ## Step 3: Run a verification submission Visit the page hosting your Forminator element in an **incognito window**, with `?utm_source=test&utm_medium=verify&utm_campaign=forminator-check` tacked onto the URL. Submit a real entry using an inbox you control. Within seconds, the entry should appear in the **Contacts Hub** in SourceLoop with the three test UTM values stored on the contact. ## Where to see Forminator submissions in SourceLoop ### Contacts Hub Each Forminator submission gets a row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click into a contact to see every page they visited, every session they had, and every campaign that touched them before they converted. ![SourceLoop Contacts Hub showing a Forminator submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the aggregate view, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Forminator submissions by source, medium, campaign, and landing page. Useful for spotting which channels actually convert and which just send tire kickers. ![SourceLoop attribution dashboard with Forminator submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) lets you build a funnel ending in "Forminator submission". Slice by source, landing page, or device to discover the highest-converting routes. ![SourceLoop funnel report ending in a Forminator submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition, mirror those Forminator submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms have real lead-generation signals to train on. Setup is covered in [Connect your Google Ads account](/help/connect-google-ads/). ## Frequently Asked Questions ### Does this also cover Forminator's quizzes, polls, and payment forms? Yes. Anything Forminator submits, contact forms, lead quizzes, polls, calculation forms, Stripe/PayPal payments, gets captured the same way. The submission event is what SourceLoop attaches attribution to. ### Does Forminator's free version work, or do I need Forminator Pro? Either works. SourceLoop runs in the browser, completely independent of which Forminator tier or add-on bundle you're on. ### I use Forminator quizzes to score leads (knowledge or personality). Are quiz completions captured? Yes. When a quiz captures an email and submits, SourceLoop treats it as a lead the same way a contact form would, attaching the visitor's full marketing journey. ### Does this interfere with my Forminator integrations (Mailchimp, ActiveCampaign, HubSpot, Zapier)? No. Those integrations continue to send submissions to your connected tools. SourceLoop layers attribution on top inside its own dashboard, no overlap. ### What about Forminator's anti-spam (Akismet, hCaptcha, honeypot)? Will those block tracking? No. Once a submission gets through your spam protection (real users always do), SourceLoop attaches its data. Submissions blocked as spam never count, which is what you want. --- # How to track lead source in Happyforms Layer marketing attribution on every Happyforms submission so you can finally see which channel earned each lead, not just that the form was sent. Source: https://sourceloop.ai/help/track-lead-source-in-happyforms/ Updated: 2026-05-28 --- Happyforms aims to be the form plugin you can hand to a non-technical client without flinching, clean UI, sensible defaults, and a strong opinion about UX. What it leaves to the marketer is figuring out where the leads actually came from. SourceLoop bolts that piece on without rewriting anything. Three lightweight steps, five minutes of work, and every Happyforms submission afterwards is tied back to its acquisition source. ## What SourceLoop captures from Happyforms After installation, here's what gets attached to every Happyforms submission: - **The marketing source** that drove the visit (organic, paid, referral, social, etc.) - **UTM source / medium / campaign / content / term** values from the landing URL - **Step-by-step browsing path** before the visitor hit submit - **Total time on site** leading up to the submission - **Return-visit count** prior to the conversion - **Email and name** from the Happyforms fields - **First-touch landing page** of the visitor's history - **Source of the converting session** specifically - **Device, country, and browser** signals ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or any other way to add markup to ``) - A **Happyforms form** embedded on a published page ## Step 1: Install the SourceLoop snippet Go to **Setup -> Tracking code** in the SourceLoop sidebar and copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to your WordPress site's ``. Any of these work: - A header injection plugin like "Insert Headers and Footers" or "WPCode" - Direct edit to your active theme's `header.php` (just before ``) - The "Custom Code" or "Tracking" panel inside your SEO plugin - Google Tag Manager set to fire on all pages Wherever you put it, the snippet should run on every page that contains a Happyform. ## Step 2: Confirm the form is on a publicly-tracked page Happyforms doesn't need any per-form configuration to play nicely with SourceLoop. Once the snippet is live, every published page with a Happyform on it gets attributed automatically. Worth double-checking: - The form is on a **published** page or post (not a draft or scheduled future post) - The form **collects an email address** (SourceLoop treats email as the lead key) - Page-cache or script-defer plugins aren't loading SourceLoop after the form submits > **Happyforms preview / shortcode-only URLs aren't attributable** > If you share a direct preview URL or a page that lives outside your normal site (no SourceLoop snippet), submissions still arrive in Happyforms but show up in SourceLoop without a source. Always direct campaign traffic to a real page on your site. ## Step 3: Test with a real submission Pop the form's page open in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=happy-check` glued onto the URL, then submit using a real email address. Within a few seconds, that submission should show up at the top of the **Contacts Hub** in SourceLoop with all three UTM values stamped on the record. ## Where to see Happyforms submissions in SourceLoop ### Contacts Hub Each Happyforms submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a row to see the full pre-submission journey: every page the lead visited, every session they had, every campaign that touched them. ![SourceLoop Contacts Hub showing a Happyforms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the broader picture, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups all your Happyforms submissions by source, medium, and campaign. A quick scan tells you which channels are actually pulling weight. ![SourceLoop attribution dashboard with Happyforms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), configure a funnel that ends in "Happyforms submission". Cut it by source or landing page to see which paths drive the highest conversion rate. ![SourceLoop funnel report ending in a Happyforms submission conversion step](/help/screenshots/sourceloop-funnel.png) If your stack includes paid acquisition, forward Happyforms submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the algorithms optimise toward leads, not clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Will this work with the free Happyforms plugin? Yes. SourceLoop sits entirely in the browser, so the Happyforms tier you're on (free or Upgrade) has no effect on tracking. ### Happyforms is known for its design polish. Will the snippet break my forms visually? No. The SourceLoop snippet doesn't touch the DOM, render UI, or load any CSS. Your Happyforms styling stays exactly as designed. ### I use Happyforms with a custom theme. Does that change setup? No. The snippet only needs to be in your theme's `` (or injected by a plugin). Any theme that follows WordPress conventions is fine. ### Can I track Happyforms submissions on a WooCommerce or membership page? Yes. As long as the page hosting the Happyform also serves the SourceLoop snippet, attribution attaches normally, whether the form is on a public landing page, a gated members area, or a checkout step. ### My Happyforms form sends notifications via email. Will those still work? Yes. Happyforms continues to dispatch its email notifications and any connected webhooks. SourceLoop runs in parallel and adds attribution data on its end. --- # How to track lead source in Cognito Forms Get the missing marketing dimension on every Cognito Forms entry, the channel, campaign, and landing page behind it, plus the full journey. Source: https://sourceloop.ai/help/track-lead-source-in-cognito-form/ Updated: 2026-05-28 --- Cognito Forms is the SaaS form builder that punches well above its price tier, calculations, signatures, payments, document templates, all on a famously generous free plan. The single thing it doesn't do is tell you which marketing channel sent each lead. SourceLoop closes that gap with no Cognito-side configuration. Three quick steps, around five minutes, and from then on every form fill carries its acquisition story. ## What SourceLoop captures from Cognito Forms Each submission flows into SourceLoop bundled with: - **The marketing source** that delivered the visitor (paid, organic, social, referral, direct, etc.) - **UTM parameters**: source, medium, campaign, term, content - **Page sequence** the visitor stepped through before submitting - **Session-level time on site** ahead of the conversion - **Visit count** prior to the form fill - **Email and name** read from the Cognito Forms fields - **First-touch landing page** of the visitor's history - **Source for the converting session** (often distinct from first-touch) - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the page where your Cognito Form is embedded - A **Cognito Forms account** with a form you can embed via the script embed ## Step 1: Add SourceLoop's tracking snippet to your site From SourceLoop, head to **Setup -> Tracking code** in the left nav and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into the `` of your site. Site-wide is best, but at minimum it needs to run on every page where you plan to embed a Cognito Form. WordPress users have header-injection plugins; static site builds have layout templates; Webflow/Framer have project-level head settings; tag managers can fire it on all pages. ## Step 2: Use the JavaScript embed on a tracked page In Cognito Forms, open your form, click **Publish -> Embed Form**, and copy the **seamless embed** snippet (the JavaScript version, not the iframe). Paste that snippet into the page where your form should appear. The page must also have the SourceLoop snippet from Step 1. > **Iframe embeds and direct cognitoforms.com URLs aren't ideal** > The iframe embed isolates the form in a separate browsing context, which makes attribution less reliable. The hosted `cognitoforms.com/...` URL skips your site entirely, so there's no SourceLoop pageview to tie the submission to. Stick with the seamless JavaScript embed on a page that loads SourceLoop. ## Step 3: Send a verification submission Open the page hosting your form in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=cognito-check` to the URL, and submit a test entry with an email address you can check. Within seconds, the entry should show up at the top of the **Contacts Hub** in SourceLoop with the three test UTMs attached. ## Where to see Cognito Forms submissions in SourceLoop ### Contacts Hub Every Cognito Forms submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click a contact to see their complete browsing path: every page, every prior session, every touchpoint that led to the submission. ![SourceLoop Contacts Hub showing a Cognito Forms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Pop open [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) for the cross-channel view. Cognito submissions get grouped by source, medium, and campaign so you can compare what's driving leads vs. what's only driving traffic. ![SourceLoop attribution dashboard with Cognito Forms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel that ends in "Cognito Forms submission". Slice by source, landing page, or device to surface the highest-converting routes from first visit to form fill. ![SourceLoop funnel report ending in a Cognito Forms submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is part of your mix, mirror Cognito submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on actual leads. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work on Cognito Forms' free plan? Yes. Cognito Forms' generous free plan supports embedding forms on external sites, which is the only requirement on the Cognito side. SourceLoop attaches attribution regardless of your Cognito plan level. ### I embed Cognito Forms with a JavaScript snippet vs an iframe. Does it matter? The JavaScript "seamless" embed is what we recommend. Iframes complicate cross-frame data sharing, so attribution is most reliable when the form renders inline on your page via the script embed. ### Are Cognito Forms' calculation, conditional-logic, and document-generation features safe to use? Yes. Everything you've built inside Cognito Forms, calculations, dynamic fields, document templates, signatures, payments, runs untouched. SourceLoop only attaches marketing context to the submission itself. ### I link directly to a Cognito Forms hosted URL (cognitoforms.com/...). Will those submissions be attributed? No. Direct links to the Cognito-hosted version of your form bypass your site entirely, so there's no SourceLoop tracking on those pageviews. Embed the form on your own page and send traffic there instead. ### Does this conflict with Cognito Forms' built-in spam protection (CAPTCHA, profanity filter)? No. Spam protection runs entirely inside Cognito Forms. SourceLoop only attaches attribution to submissions that successfully complete, which is what you want. --- # How to track lead source in Omnisend Find out which channel grew your Omnisend list, lead by lead, by tying every signup form, popup, and wheel-of-fortune capture back to its true source. Source: https://sourceloop.ai/help/track-lead-source-in-omnisend/ Updated: 2026-05-28 --- Omnisend is the email and SMS marketing platform of choice for a lot of Shopify and WooCommerce stores, generous free tier, gorgeous templates, and capture forms (popups, embeds, the famous wheel of fortune) built in. What it doesn't tell you is which marketing channel actually grew that list. SourceLoop bolts that data onto every Omnisend signup. Three quick steps, around five minutes, and every new Omnisend subscriber afterwards comes tagged with its acquisition channel. ## What SourceLoop captures from Omnisend For each Omnisend form fill (popup, embed, landing page, wheel-of-fortune, whichever), SourceLoop attaches: - **The traffic source** the visitor arrived through (paid, organic, social, referral, etc.) - **UTM parameters** parsed from the landing URL - **Pages visited** in chronological order before the signup - **Total time on site** during the visit (or visits) - **Number of prior sessions** before they finally subscribed - **Email and name** captured from the Omnisend form - **First-touch landing page** at the top of the visitor's history - **Last-session source**, the campaign that finally converted them - **Device, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Theme edit access** on your store (Shopify, WooCommerce, BigCommerce, Wix, custom build) - An **Omnisend form** (popup, embedded, or landing page) live on your store ## Step 1: Install SourceLoop's tracking snippet on your store Inside SourceLoop, go to **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into the `` of your storefront: - **Shopify**: Online Store -> Themes -> Edit code -> `theme.liquid`, drop the snippet just before `` - **WooCommerce**: a header-injection plugin like "Insert Headers and Footers" or "WPCode" - **BigCommerce**: Storefront -> Script Manager, add as a script that loads in `` on all pages - **Wix**: Settings -> Custom Code -> Add Custom Code, set placement to `` and apply site-wide - **Custom build**: paste into your global layout template's `` Wherever you put it, the snippet has to load on every page where an Omnisend form might appear, which is usually every storefront page. ## Step 2: Confirm your Omnisend forms are running on tracked pages Omnisend forms don't need any per-form configuration on the SourceLoop side. Once the snippet loads on the page, Omnisend captures flow into SourceLoop automatically. Worth verifying: - Your Omnisend forms are **active and live** (Forms tab in Omnisend, status "Active") - The form **captures an email** (SMS-only forms work too if they collect an email at any point) - Your storefront isn't deferring SourceLoop with an extreme delay (e.g., loading on user interaction only) > **Omnisend-hosted landing pages aren't on your domain** > Omnisend's own landing-page builder serves pages off Omnisend's domain, not yours. Those visitors never load your tracking script, so signups from those landing pages won't have a marketing source attached. Use Omnisend's embedded form or popup on a page of your own store instead. ## Step 3: Run a verification opt-in Visit your storefront in an **incognito window** with `?utm_source=test&utm_medium=verify&utm_campaign=omnisend-check` glued onto the URL. Trigger the Omnisend form (scroll, exit-intent, time-on-page, whatever the trigger is) and subscribe with an email you can check. Within seconds, the new subscriber should appear in the **Contacts Hub** in SourceLoop, with the test UTM values pinned to the record. ## Where to see Omnisend signups in SourceLoop ### Contacts Hub Every Omnisend signup lands as a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Expand a row to see how that subscriber arrived, what pages they browsed, and which campaign earned the opt-in. ![SourceLoop Contacts Hub showing an Omnisend signup with the visitor's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Omnisend signups by source, medium, and campaign. Spot which paid channels grow the list cheaply and which ones just burn budget. ![SourceLoop attribution dashboard with Omnisend signups grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel that ends in "Omnisend signup". Cut by source, content, or landing page to find the highest-converting routes from first visit to subscriber. ![SourceLoop funnel report ending in an Omnisend signup conversion step](/help/screenshots/sourceloop-funnel.png) If you're running paid acquisition, forward Omnisend signups back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn from actual list growth instead of clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this cover Omnisend popups, embedded forms, and the wheel-of-fortune capture? Yes. Whichever Omnisend capture format you use, classic popup, embedded form, landing page, gamified wheel, the signup event flows through SourceLoop the same way, as long as it loads on a page that has the tracking script. ### I run Omnisend on a Shopify store. Does that change setup? No. The SourceLoop snippet goes into your Shopify theme's `` (Theme code -> theme.liquid -> the head section). Once it's live, every Omnisend form on every page becomes attributable. ### I also use Omnisend for SMS opt-ins. Are SMS subscribers tracked? Yes, if the SMS opt-in happens through an Omnisend form on your site that captures an email or phone number. The visitor's marketing source is attached to the contact record. ### Will this conflict with Omnisend's existing campaign reporting? No. Omnisend's email and SMS campaign analytics keep working as before. SourceLoop adds top-of-funnel attribution (where the subscriber originally came from), which Omnisend itself doesn't track. ### Are my Omnisend automations and workflows affected? No. Welcome series, abandoned cart, browse abandonment, none of it changes. SourceLoop simply enriches the contact with marketing source data before Omnisend's automations fire. --- # How to track lead source in Paperform Match every Paperform submission with the marketing channel that earned it, all without touching your form or signing up for a separate analytics tool. Source: https://sourceloop.ai/help/track-lead-source-in-paperform/ Updated: 2026-05-28 --- Paperform built a reputation as the form-meets-landing-page hybrid: forms that look like content, mixing text, images, and questions in one flowing layout. Marketers love the design freedom but they still miss one thing, knowing which campaign each submission came from. SourceLoop fills in that gap. Three steps, around five minutes of setup, and Paperform submissions afterwards arrive with the story of how that visitor found you. ## What SourceLoop captures from Paperform Once installed, every Paperform submission carries: - **The visitor's acquisition channel** (organic, paid, social, referral, etc.) - **Complete UTM set** lifted from the landing URL - **Pages browsed** in sequence before the form fill - **Cumulative time on site** prior to the submission - **Number of distinct sessions** before they converted - **Email and name** captured from your Paperform questions - **First-touch landing page** at the top of the visitor's history - **Source of the converting session**, often different from first-touch - **Device, location, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the page where you embed your Paperform form - A **Paperform account** with at least one form published and embeddable ## Step 1: Drop SourceLoop's tracking snippet into your site's head From SourceLoop, click **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Put it in the `` of the page (or whole site) where your Paperform will live. WordPress users: a header-injection plugin or `header.php`. Webflow: Project Settings -> Custom Code -> Head Code. Framer: site-level Custom Code. Static sites: your global layout. Tag manager: an All Pages tag. Whichever path, the snippet just needs to run before the Paperform form does. ## Step 2: Embed your Paperform on a tracked page In Paperform, open your form and click **Share -> Embed**. You'll get three formats: - **Inline embed**: drops the form straight onto the page - **Slider**: opens from the side - **Popup**: opens as a modal overlay Pick whatever fits your design, copy the embed snippet, and paste it into your page where the form should appear. Make sure that page also has the SourceLoop snippet from step 1. > **Direct paperform.co URLs aren't attributable** > A raw `paperform.co/` link sends visitors straight to Paperform's domain, skipping your site entirely. Those submissions won't carry a marketing source. Route campaigns to a page you control that embeds the form. ## Step 3: Submit a test entry Open your form's host page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=paperform-check` glued to the URL. Submit a real entry using an email address you can check. Within a few seconds, the submission should land on the **Contacts Hub** in SourceLoop, with all three test UTM values shown on the contact. ## Where to see Paperform submissions in SourceLoop ### Contacts Hub Every Paperform submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click a row to see the visitor's complete browsing history before they filled the form. ![SourceLoop Contacts Hub showing a Paperform submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Open [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) for the cross-channel rollup. Paperform submissions are grouped by source, medium, and campaign so you can read which channels are converting. ![SourceLoop attribution dashboard with Paperform submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Configure a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) with "Paperform submission" as the final step. Slice by source, content, or device to find which paths actually drive form fills. ![SourceLoop funnel report ending in a Paperform submission conversion step](/help/screenshots/sourceloop-funnel.png) If you advertise, forward Paperform submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the platforms can optimise toward real form fills. The [Connect your Google Ads account](/help/connect-google-ads/) guide covers it. ## Frequently Asked Questions ### Paperform forms feel more like full pages than forms. Does that affect attribution? Not at all. The "page-like" feel is just Paperform's editor model. What matters for attribution is where the form is rendered, embed it on your site (vs. linking to the paperform.co hosted URL) so SourceLoop can see the visitor first. ### I use Paperform's payment, calculation, and conditional features. Anything special? No. All your Paperform logic, prices, calculations, conditional questions, runs on Paperform's side. SourceLoop only attaches marketing context to the final submission, no logic interference. ### Does this work with embedded forms in popup, slider, and inline modes? Yes. Inline, slider, and popup embed types all fire submissions the same way once a visitor completes the form on your page. ### I share Paperform links on social and email. Will those submissions still get attributed? A raw paperform.co URL won't, because the visitor never lands on your tracked site. Send campaigns to a page on your domain that embeds the form so SourceLoop can pick up the source. ### Will my Paperform integrations (Zapier, webhooks, Mailchimp, HubSpot) keep working? Yes. Paperform continues to push submissions to all your connected integrations exactly as configured. SourceLoop runs in parallel on its own data store. --- # How to track lead source in QuestionScout Layer marketing attribution on top of every QuestionScout form so each response carries the source, campaign, and journey that produced it. Source: https://sourceloop.ai/help/track-lead-source-in-questionscout/ Updated: 2026-05-28 --- QuestionScout sits in the prosumer form-builder tier, deep logic, generous customisation, professional themes, and a famous AppSumo lifetime deal. The blind spot it shares with every form tool is acquisition data, you see the answers, never the channel. This guide solves that for QuestionScout submissions. Three steps, around five minutes, no configuration required inside QuestionScout itself. ## What SourceLoop captures from QuestionScout For each QuestionScout form fill, SourceLoop attaches: - **Marketing source** (organic, paid, referral, social, direct) - **Full UTM parameter set** parsed from the landing URL - **Sequence of pages browsed** before submission - **Time spent on site** during the visit (or visits) leading up - **Repeat-visit count** before they finally responded - **Email and name** pulled from your QuestionScout fields - **First-touch landing page** the lead originally arrived on - **Source attributed to the converting session** specifically - **Device type, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the page where your QuestionScout form is embedded - A **QuestionScout account** with a form published and embeddable ## Step 1: Install the SourceLoop snippet Inside SourceLoop, head to **Setup -> Tracking code** in the left sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to the `` of the site (or pages) hosting your QuestionScout embed. WordPress: header-injection plugin or `header.php`. Webflow: Project Settings -> Custom Code -> Head Code. Framer: site-level Custom Code. Static sites: your global layout template. Tag manager: an All Pages tag. The snippet just needs to load on every page where a QuestionScout form might appear. ## Step 2: Embed your QuestionScout form on a tracked page In QuestionScout, open your form, click **Share -> Embed**, and copy the embed snippet. Paste it into the page where the form should appear, and make sure that page also has the SourceLoop snippet from step 1. > **QuestionScout-hosted form URLs aren't attributable** > A bare QuestionScout public form URL (the share link that hosts the form on QuestionScout's domain) skips your site entirely, so SourceLoop never gets a chance to see the visitor's source. Route campaigns to a page on your domain that embeds the form instead. ## Step 3: Send a test response Visit your form's page in an **incognito tab**, append `?utm_source=test&utm_medium=verify&utm_campaign=questionscout-check` to the URL, and submit a real response with an email you can access. Within seconds, the new lead should appear at the top of the **Contacts Hub** in SourceLoop, with the test UTM values pinned to the record. ## Where to see QuestionScout submissions in SourceLoop ### Contacts Hub Each response becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Drill into a contact to see their entire pre-response journey, every page they read, every campaign that touched them. ![SourceLoop Contacts Hub showing a QuestionScout submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups QuestionScout submissions by source, medium, and campaign. A quick read on what's bringing in respondents vs. what's just bringing in traffic. ![SourceLoop attribution dashboard with QuestionScout submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "QuestionScout submission". Cut by source, landing page, or device to expose your highest-converting paths. ![SourceLoop funnel report ending in a QuestionScout submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is in the mix, forward QuestionScout submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the auction algorithms train on real form fills instead of clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### QuestionScout has deep logic, calculations, and themes. Will any of that break? No. Everything you've built inside QuestionScout (skip logic, calculations, hidden fields, custom themes) runs the same way. SourceLoop simply tags the final submission with marketing data, it doesn't interact with the form internals. ### Does this work on the QuestionScout AppSumo lifetime deal? Yes. The tier you're on doesn't matter, SourceLoop runs in the browser, completely outside QuestionScout's billing tiers and feature gates. ### I use QuestionScout's embed code on my landing page. Is that the right path? Yes. The embed-on-your-site model is exactly what attribution needs, the visitor lands on a SourceLoop-tracked page, then fills the form there. The QuestionScout-hosted public form URL would skip your tracking. ### Are my QuestionScout webhook and Zapier integrations affected? No. Submissions continue to fire your webhooks, Zaps, and any other connected destinations exactly as configured. SourceLoop captures the lead independently on its own backend. ### Can QuestionScout's "Hidden Fields" feature be used to pass UTMs into the submission record? You can do that natively in QuestionScout, but it's not required for SourceLoop. SourceLoop already captures UTMs at the session level and attaches them to the lead automatically, so you don't have to wire hidden fields specifically for attribution. --- # How to track lead source in AidaForm Stamp every AidaForm response with the marketing source, campaign, and pre-submission journey so you finally know which channels grow your lead list. Source: https://sourceloop.ai/help/track-lead-source-in-aidaform/ Updated: 2026-05-28 --- AidaForm has carved out a niche as the form-and-survey builder that's friendly to non-designers, conversational layouts, an order-form template library, and integrations across the standard SaaS stack. What it doesn't surface, like every form tool, is where the responses came from. SourceLoop hooks that up. Three steps, under ten minutes total, with nothing to configure on AidaForm's side. ## What SourceLoop captures from AidaForm Once you're set up, every AidaForm response shows up in SourceLoop with this context attached: - **Acquisition channel** the visitor came from - **UTM parameters** parsed from the landing URL - **Browsing path** captured in order before the submission - **Time spent on your site** across all pre-submission sessions - **Number of return visits** before the form fill - **Email and name** captured from the AidaForm fields - **First-touch landing page** of the visitor's history - **Source of the converting session** specifically - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the page where you embed your AidaForm - An **AidaForm** form published and embeddable via the script snippet ## Step 1: Add SourceLoop's tracking snippet to your site Open SourceLoop, go to **Setup -> Tracking code** in the left sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet into your site's ``. Common paths: - **WordPress**: a header-injection plugin or your theme's `header.php` - **Webflow**: Project Settings -> Custom Code -> Head Code - **Framer**: Site Settings -> General -> Custom Code -> Start of head - **Static / custom build**: the global layout template - **Tag manager**: a Custom HTML tag firing on All Pages The snippet should run on every page where an AidaForm form might appear. ## Step 2: Embed your AidaForm on a tracked page In AidaForm, open your form and click **Publish -> Embed Code**. Copy the embed snippet and drop it into your page where the form should live. The page must also include the SourceLoop snippet from step 1. > **AidaForm-hosted form URLs aren't attributable** > AidaForm's public form link (a URL on aidaform.com) hosts the form on AidaForm's domain, so visitors land there instead of your site, and SourceLoop never sees the source. Route campaigns to a page on your domain that embeds the form. ## Step 3: Run a test submission Open the page hosting your AidaForm in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=aida-check` appended to the URL. Submit a real response using an email you can check. Within seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see AidaForm submissions in SourceLoop ### Contacts Hub Each AidaForm response becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click into a contact to see the visitor's complete pre-response browsing timeline. ![SourceLoop Contacts Hub showing an AidaForm submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the aggregate view, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups AidaForm responses by source, medium, and campaign. Quickly compare paid channels vs. organic vs. referral to see where leads come from at scale. ![SourceLoop attribution dashboard with AidaForm submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) with "AidaForm submission" as the final step. Slice by source, landing page, or device to find your highest-converting paths. ![SourceLoop funnel report ending in an AidaForm submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid campaigns, mirror AidaForm responses back to **Google Ads, Meta, and LinkedIn as offline conversions** so the auctions optimise toward actual lead generation, not clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Does this work with AidaForm's free plan? Yes. SourceLoop is plan-agnostic on AidaForm's side, free, Premium, Business, all behave the same way once the form is embedded on a tracked page. ### I use AidaForm's conversational-style forms (one question at a time). Will those be captured? Yes. Whichever AidaForm layout you ship, classic, step-by-step, or conversational, the final submission event carries the visitor's attribution data exactly the same. ### AidaForm has a payment field via Stripe and PayPal. Are paid submissions captured? Yes. Once the visitor completes payment and the form submits, SourceLoop tags that lead with the marketing source. The payment processor flow itself happens inside AidaForm and isn't affected. ### Can I use this on an AidaForm survey hosted on my own subdomain? Yes. As long as the page hosting the form loads the SourceLoop snippet (your subdomain counts as your site), submissions are attributed normally. ### Will my AidaForm Zapier, Google Sheets, and webhook integrations keep working? Yes. AidaForm continues to push submissions to all your configured destinations. SourceLoop adds an attribution-rich copy of the lead to its own backend, no overlap or conflict. --- # How to track lead source in ARForms Stop losing the marketing context behind every ARForms submission. Tie each lead back to the campaign and journey that delivered it. Source: https://sourceloop.ai/help/track-lead-source-in-arforms/ Updated: 2026-05-28 --- ARForms is the WordPress form plugin that leans into aesthetics, slick AJAX submissions, premium templates, drag-and-drop builder, and a CodeCanyon following. Useful for agencies and lead-gen sites, but it doesn't say which marketing channel produced each entry. SourceLoop fills in that missing field. Three steps, around five minutes, no ARForms-side configuration required. ## What SourceLoop captures from ARForms Every ARForms submission lands in SourceLoop tagged with: - **Acquisition source** of the visitor (paid, organic, social, referral, direct) - **All UTM parameters** parsed from the landing URL - **Page-by-page browsing path** taken before the form fill - **Cumulative time on site** ahead of the conversion - **Number of distinct sessions** before the submission - **Email + name** from the ARForms fields - **Landing page** of the visitor's first session - **Source of the converting session** (often distinct from first-touch) - **Device, country, browser** of the lead ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to edit `` markup) - An **ARForms** form embedded on a published WordPress page ## Step 1: Install SourceLoop's snippet on your WordPress site From SourceLoop, head to **Setup -> Tracking code** in the sidebar and copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Drop it into the `` of your WordPress site. The usual WordPress paths: - A header-injection plugin (Insert Headers and Footers, WPCode) - Your active theme's `header.php`, just before `` - A "Custom Code" field inside Yoast, Rank Math, or SEOPress - Google Tag Manager set to fire on All Pages The script must load on every page that contains an ARForms form. ## Step 2: Confirm the form is on a tracked, published page There's no per-form switch to flip inside ARForms. Once the snippet is live, every ARForms form on every page that includes the snippet is attributable. Worth confirming: - The form is on a **published page or post** (not a draft or private page) - The form **collects an email address**, SourceLoop uses email as the lead ID - Any performance plugin isn't deferring SourceLoop past the form's submit handler > **Preview-only links won't carry attribution** > Sharing a WordPress preview URL or any direct link to a page that excludes the SourceLoop snippet (a draft, a private page, a staging subdomain you didn't set up) means submissions show up in ARForms but appear without a source in SourceLoop. Always route campaigns to a fully published page. ## Step 3: Submit a test entry to verify Open the form's page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=arforms-check` glued to the URL. Submit a real entry using an email you can check. Within seconds, the submission should land on the **Contacts Hub** in SourceLoop with the three test UTM values shown on the record. ## Where to see ARForms submissions in SourceLoop ### Contacts Hub Each submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact to see the full timeline, every page visited, every prior session, every campaign that touched them before the submission. ![SourceLoop Contacts Hub showing an ARForms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls up ARForms submissions by source, medium, and campaign. A quick read on which channels are actually pulling weight, vs. which ones just send traffic. ![SourceLoop attribution dashboard with ARForms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) with "ARForms submission" as the final step. Slice by source, content, or device to find the highest-converting paths from first visit to form fill. ![SourceLoop funnel report ending in an ARForms submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is in the mix, forward ARForms submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the auction algorithms train on real lead generation. [Connect your Google Ads account](/help/connect-google-ads/) covers the setup. ## Frequently Asked Questions ### ARForms uses AJAX submissions by default. Does that affect SourceLoop? No. Whether ARForms submits via AJAX or a full page reload, SourceLoop attaches attribution to the submission event the same way. ### Can I track ARForms used in popup, slider, or modal mode? Yes. ARForms' popup, modal, and slider variants all fire submissions the same way once the visitor reaches the end of the form. ### My ARForms form is on a page protected by a membership plugin. Will tracking work? Yes, as long as the SourceLoop snippet loads on that protected page. If the membership plugin defers script loading aggressively, place the SourceLoop snippet outside the deferred bundle. ### Are ARForms' Mailchimp, ActiveCampaign, GetResponse, and webhook connections affected? No. All your existing ARForms integrations continue to fire as configured. SourceLoop layers attribution data on top in its own dashboard, without intercepting anything ARForms does. ### I custom-styled my ARForms with CSS. Does the snippet impact styling? No. SourceLoop's snippet is invisible, no DOM injection, no CSS, no styling impact whatsoever. Your ARForms designs render exactly as before. --- # How to track lead source in Everest Forms Wire Everest Forms submissions into proper marketing attribution. Every entry arrives tagged with source, campaign, and the visitor's complete journey. Source: https://sourceloop.ai/help/track-lead-source-in-everest-forms/ Updated: 2026-05-28 --- Everest Forms is the gentler entry point into WordPress form-building, free, lightweight, and approachable. Where most marketers eventually hit a wall is the moment leads start arriving and there's no built-in way to see which channel sent them. SourceLoop solves exactly that, with no Everest Forms changes required. Three steps, around five minutes, attribution active on every form on the site. ## What SourceLoop captures from Everest Forms Each Everest Forms submission flows into SourceLoop tagged with: - **The marketing source** of the visitor's session - **Full UTM stack** from the landing URL - **Pages browsed** in order before the form was submitted - **Time on site** ahead of the submission - **Repeat visits** that preceded this conversion - **Email + name** captured from the Everest Forms fields - **Original landing page** of the visitor's history - **Source of the converting session** specifically - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to edit `` markup) - An **Everest Forms** form embedded on a published WordPress page ## Step 1: Drop the SourceLoop snippet into WordPress In SourceLoop, open **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) On WordPress, place it in your site's ``. The usual options: - WPEverest's own User Registration suite has a header-script slot you can use - A standalone header-injection plugin (Insert Headers and Footers, WPCode) - Your active theme's `header.php`, just before `` - The "Custom Code" section of any SEO plugin - Google Tag Manager firing on All Pages Whichever path, the snippet should run on every page where an Everest Form may appear. ## Step 2: Confirm your form is on a published page No per-form settings to flip inside Everest Forms. Once the snippet loads site-wide, every Everest Form on every published page becomes attributable automatically. A quick sanity pass: - The form is on a **published** page or post (drafts aren't accessible to visitors) - The form **collects an email field**, SourceLoop uses email to create the lead - Performance plugins (WP Rocket, W3 Total Cache, etc.) aren't defer/async-ing SourceLoop after the form submits > **Submissions through unpublished or password-protected pages aren't attributable** > Pages behind a password (or that haven't been published yet) typically don't serve the SourceLoop snippet to anonymous visitors. Submissions on those pages reach Everest Forms but won't have a marketing source in SourceLoop. Publish the page, or ensure the snippet runs on protected pages too. ## Step 3: Send a test submission Open your form's page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=everest-check` glued onto the URL. Submit a test entry using an email you control. Within seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values pinned to the contact. ## Where to see Everest Forms submissions in SourceLoop ### Contacts Hub Each Everest Forms entry becomes a row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click a row to see the visitor's complete pre-submission browsing path. ![SourceLoop Contacts Hub showing an Everest Forms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the cross-channel rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Everest Forms submissions by source, medium, and campaign so you can compare what's converting. ![SourceLoop attribution dashboard with Everest Forms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel that ends in "Everest Forms submission". Slice it by source, landing page, or device to find the highest-converting paths. ![SourceLoop funnel report ending in an Everest Forms submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is in your stack, mirror Everest Forms submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the ad platforms train on real lead generation instead of clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work on the free Everest Forms plugin or do I need Pro? Both. SourceLoop's tracking runs entirely in the browser, with no dependency on Everest Forms' add-ons or paid features, so the free plugin is fully supported. ### I use Everest Forms' Multi-Part Forms add-on. Are multi-step submissions captured? Yes. The number of steps inside the form doesn't matter, SourceLoop fires when the visitor reaches the final submit, regardless of how many sections came before. ### My Everest Forms send data to a connected User Registration plugin to create accounts. Will that flow still work? Yes. The User Registration plugin continues to create accounts normally. SourceLoop attaches attribution to the form submission on its end, completely independent of the registration workflow. ### Can SourceLoop track entries from Everest Forms' Surveys, Quizzes, and Polls add-on? Yes, as long as the survey or quiz captures an email at some point. SourceLoop uses email to identify the lead and attach the marketing source. ### Will the SourceLoop snippet slow down my Everest Forms pages? No. The snippet is small and loads asynchronously, so it doesn't block your page render or your form's submission flow. --- # How to track lead source in FormAssembly Bring marketing attribution to FormAssembly without compromising compliance. Submissions flow where they always did, with source attached. Source: https://sourceloop.ai/help/track-lead-source-in-formassembly/ Updated: 2026-05-28 --- FormAssembly sits at the enterprise end of the form-builder market, the choice when forms touch regulated data, Salesforce, or compliance-heavy workflows. The compliance and routing story is well-handled. The marketing-attribution story isn't, you can see what was submitted, never how the submitter arrived. SourceLoop adds that layer without touching FormAssembly's plumbing. Three steps, around ten minutes, attribution flowing on every form afterwards. ## What SourceLoop captures from FormAssembly For each FormAssembly submission, SourceLoop attaches: - **The visitor's acquisition channel** (organic, paid, referral, social, direct) - **UTM parameters** parsed from the landing URL - **Pages visited** in chronological order before submission - **Total time on site** ahead of the conversion - **Visit count** before the lead finally submitted - **Email + name** read from the FormAssembly fields - **First-touch landing page** of the visitor's history - **Source of the converting session** specifically - **Device type, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the public page where you embed your FormAssembly form - A **FormAssembly account** with a form configured to embed via the JavaScript snippet ## Step 1: Install SourceLoop's tracking snippet Inside SourceLoop, click **Setup -> Tracking code** in the sidebar and copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to your marketing site's ``. WordPress: header-injection plugin or `header.php`. Marketing-cloud-style CMS (Adobe, Sitecore, Drupal): global template / layout. Webflow / Framer: site-level Custom Code. Tag manager: an All Pages tag. The snippet should run on every public-facing page where a FormAssembly form might appear. ## Step 2: Embed FormAssembly with the JavaScript embed on a tracked page In FormAssembly, open your form, click **Publish** and choose the JavaScript embed (not the iframe). Copy the snippet and paste it into the page where your form should appear, alongside the SourceLoop snippet from step 1. > **Hosted FormAssembly URLs and iframe embeds reduce attribution accuracy** > A direct FormAssembly hosted URL (a link straight to `tfaforms.com/...`) skips your marketing site entirely, so SourceLoop never sees the visitor. Iframe embeds isolate the form from your page's tracking context, which can also break attribution. The JavaScript embed on a page of your own site is the cleanest path. ## Step 3: Run a verification submission Visit the form's host page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=formassembly-check` appended to the URL. Submit a test entry using an email you can check. Within seconds, the lead should appear in the **Contacts Hub** in SourceLoop with the three test UTM values attached. ## Where to see FormAssembly submissions in SourceLoop ### Contacts Hub Each FormAssembly submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Drill into a contact for the full timeline, every page visited, every session, every campaign that touched the lead. ![SourceLoop Contacts Hub showing a FormAssembly submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For executive reporting, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups FormAssembly submissions by source, medium, and campaign. Useful for justifying marketing spend at quarterly reviews. ![SourceLoop attribution dashboard with FormAssembly submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "FormAssembly submission". Slice by source, content, or device to expose your highest-converting routes through the site. ![SourceLoop funnel report ending in a FormAssembly submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid campaigns, mirror FormAssembly submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real qualified leads instead of clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through it. ## Frequently Asked Questions ### We use FormAssembly because of HIPAA / GDPR compliance. Does adding SourceLoop affect that? No. FormAssembly's compliance posture (HIPAA, GDPR, FERPA, etc.) covers how it stores and routes form data. SourceLoop runs on your marketing site, capturing visitor session metadata for non-PHI attribution purposes. The two systems don't share infrastructure. ### I embed FormAssembly via JavaScript vs iframe. Does it matter? The JavaScript embed is preferred because the form renders inline on your page, so attribution is straightforward. Iframe embeds isolate the form in a separate browsing context, which can make attribution less reliable depending on your iframe permissions. ### Our FormAssembly forms push directly to Salesforce, Microsoft Dynamics, or our data warehouse. Anything affected? No. FormAssembly's downstream connectors continue to push data exactly where they always have. SourceLoop captures attribution independently in its own backend, no overlap with your CRM or warehouse routing. ### We use FormAssembly's Hidden Fields to pre-populate UTMs into Salesforce. Should we keep doing that? You can. The two approaches complement each other, Hidden Fields put UTMs onto the Salesforce record, SourceLoop attaches the visitor's full pre-submission journey to a contact in its own dashboard. Use whichever surface the data needs to live in. ### Are FormAssembly's Workflow forms (multi-form processes) tracked? The final submission of the workflow is what SourceLoop captures, since that's the conversion. Intermediate steps in the workflow don't create new contacts. --- # How to track lead source in Formester Pair every Formester response with the marketing channel that delivered it so you can finally answer where your leads actually come from. Source: https://sourceloop.ai/help/track-lead-source-in-formester/ Updated: 2026-05-28 --- Formester is the lightweight online form builder that's been picking up traction with small businesses and agencies looking for a Typeform alternative at a friendlier price. The capability gap is the same one every form tool has, you see the entries but never which campaign produced them. SourceLoop closes that gap with a single snippet. Three steps, under ten minutes total, no Formester-side configuration required. ## What SourceLoop captures from Formester Each Formester submission lands in SourceLoop with the following context: - **Acquisition source** of the visitor (organic, paid, referral, social, direct) - **UTM parameters** parsed from the landing URL - **Sequence of pages** browsed before the form submission - **Time on site** ahead of the conversion - **Number of separate visits** before the lead converted - **Email + name** captured from the Formester fields - **First-touch landing page** of the visitor's history - **Source attributed to the converting session** (often distinct from first-touch) - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the page where your Formester form is embedded - A **Formester** form published and embeddable via the script snippet ## Step 1: Install SourceLoop's tracking snippet on your site From SourceLoop, head to **Setup -> Tracking code** in the sidebar and copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into your site's ``. Common ways: - WordPress: a header-injection plugin or your theme's `header.php` - Webflow: Project Settings -> Custom Code -> Head Code - Framer: Site Settings -> General -> Custom Code -> Start of head - Shopify: Online Store -> Themes -> Edit code -> `theme.liquid` - Static / custom: the global layout template - Tag manager: an All Pages tag Wherever you put it, the snippet should load on every page that hosts a Formester form. ## Step 2: Embed your Formester form on a tracked page In Formester, open your form, hit **Publish -> Embed**, and copy the script embed snippet. Paste it into the page where the form should appear. That page also needs the SourceLoop snippet from step 1. > **Formester-hosted form URLs aren't attributable** > A direct Formester-hosted form link sends visitors to Formester's domain, not yours. SourceLoop never gets a chance to see the visitor's source. Always route campaign traffic to a page on your own domain that embeds the form. ## Step 3: Run a verification submission Open the page hosting your Formester form in an **incognito tab**, append `?utm_source=test&utm_medium=verify&utm_campaign=formester-check` to the URL, and submit a real entry with an email you can access. Within a few seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values pinned to the record. ## Where to see Formester submissions in SourceLoop ### Contacts Hub Each submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click into a row to see the visitor's full pre-submission browsing path. ![SourceLoop Contacts Hub showing a Formester submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the broader read, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls Formester submissions up by source, medium, and campaign so you can compare channel performance at a glance. ![SourceLoop attribution dashboard with Formester submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Formester submission". Slice by source, landing page, or device to find which routes drive the most form fills. ![SourceLoop funnel report ending in a Formester submission conversion step](/help/screenshots/sourceloop-funnel.png) If you advertise on Google, Meta, or LinkedIn, mirror Formester submissions back as **offline conversions** so the bidding algorithms train on actual lead generation. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Does this work on the free Formester plan? Yes. SourceLoop is plan-agnostic on Formester's side, so the free tier works the same as paid tiers once your form is embedded on a page that loads the tracking script. ### I use Formester's payment fields (Stripe / Razorpay). Do paid submissions get attributed? Yes. Once the visitor completes the payment and Formester records the submission, SourceLoop attaches attribution to the lead. The payment processor flow itself happens inside Formester and is unaffected. ### Can I track Formester submissions on a multi-language site? Yes. Place the SourceLoop snippet in the `` of every language variant, and submissions from any language version of the form get attributed normally. ### My Formester forms connect to Google Sheets, Slack, and webhooks. Will any of that break? No. Formester continues to forward submissions to all your configured destinations. SourceLoop saves an attribution-rich copy of the lead on its own side without touching Formester's outbound flow. ### Does Formester's conditional logic, calculations, or multi-step flow affect tracking? No. All in-form behavior runs inside Formester. SourceLoop only attaches marketing context to the final submission, so multi-step or logic-heavy forms work the same as simple contact forms. --- # How to track lead source in Formsite Add real marketing attribution to every Formsite submission so each lead is paired with the source, campaign, and journey behind it. Source: https://sourceloop.ai/help/track-lead-source-in-formsite/ Updated: 2026-05-28 --- Formsite has been around almost as long as web forms themselves, a reliable workhorse used for everything from event registrations to surveys to lead capture. The one capability it hasn't picked up over its long life is marketing attribution, you see what was submitted but not how the submitter found you. SourceLoop adds that final piece. Three steps, under ten minutes, no Formsite-side configuration needed. ## What SourceLoop captures from Formsite For every Formsite submission, SourceLoop attaches: - **Visitor's marketing source** (organic, paid, social, referral, direct) - **UTM parameters** parsed from the landing URL - **Page sequence** the visitor browsed before submitting - **Time on site** across all pre-submission sessions - **Number of return visits** before the conversion - **Email + name** from the Formsite fields - **First-touch landing page** of the visitor's history - **Source of the converting session** specifically - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the page where your Formsite form is embedded - A **Formsite account** with at least one form configured for the script embed ## Step 1: Install the SourceLoop snippet on your site Inside SourceLoop, navigate to **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to the `` of your site (or the pages that host your Formsite embed). WordPress users: header injection plugin or `header.php`. Static sites: your global layout. Webflow / Framer: Custom Code in site settings. Tag manager: an All Pages tag. The script just needs to run on every page that includes a Formsite form. ## Step 2: Embed your Formsite form on a tracked page In Formsite, open your form and pull up the **Form Code -> JavaScript Embed**. Copy the snippet and paste it into your page where the form should appear. The page also needs the SourceLoop snippet from step 1. > **Formsite-hosted form URLs aren't attributable** > Sharing a direct Formsite hosted URL routes visitors to Formsite's domain, not yours. Those submissions arrive in Formsite but show up in SourceLoop without a marketing source. Always send campaign traffic to a page on your own site that embeds the form. ## Step 3: Send a test submission Visit your form's page in an **incognito window** with `?utm_source=test&utm_medium=verify&utm_campaign=formsite-check` glued to the URL. Submit a test entry using an email you can check. Within a few seconds, the submission should land at the top of the **Contacts Hub** in SourceLoop, with the three test UTMs attached to the record. ## Where to see Formsite submissions in SourceLoop ### Contacts Hub Each submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click into a contact to see their full pre-submission browsing path, every page, every session, every campaign that touched them. ![SourceLoop Contacts Hub showing a Formsite submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Formsite submissions by source, medium, and campaign. Useful for comparing which channels actually pull leads vs. which only pull pageviews. ![SourceLoop attribution dashboard with Formsite submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "Formsite submission". Slice by source, content, or device to discover the highest-converting paths from first visit to submitted form. ![SourceLoop funnel report ending in a Formsite submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is part of your strategy, mirror Formsite submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the ad platforms optimise toward real lead generation. [Connect your Google Ads account](/help/connect-google-ads/) covers the setup. ## Frequently Asked Questions ### Formsite is one of the oldest SaaS form tools. Does its embed work the same as newer tools? Yes. Formsite's modern embed code is a standard JavaScript snippet, and SourceLoop's tracking captures submissions from it the same way as any newer form tool. ### I use Formsite for HIPAA-regulated workflows. Does adding SourceLoop affect compliance? No. Formsite's compliance posture covers the submitted form data and its storage. SourceLoop captures visitor session metadata on your marketing site for attribution purposes; it doesn't sit in the form-data path. ### Are submissions from Formsite's hosted form URL trackable? No. The Formsite-hosted URL serves the form on a Formsite domain, so visitors never load your site and SourceLoop can't see the source. Embed the form on your own page and direct campaigns there. ### Will Formsite's email notifications, Zapier hooks, and integrations keep firing? Yes. SourceLoop runs in parallel, capturing attribution on its end. Formsite continues to deliver submissions to every connected destination exactly as configured. ### My Formsite forms have multi-page navigation. Are all completed submissions captured? Yes. Only completed submissions trigger capture. The number of pages or sections inside the form doesn't change anything. --- # How to track lead source in HTML Forms Wire up source attribution for every HTML Forms entry so each submission carries the campaign, channel, and visitor journey that produced it. Source: https://sourceloop.ai/help/track-lead-source-in-html-forms/ Updated: 2026-05-28 --- HTML Forms is the no-frills WordPress plugin for developers who'd rather write their own HTML than wrestle with a visual builder, free, open-source, lightweight, and famously fast. What it doesn't do (and doesn't claim to) is tell you which marketing campaign produced each submission. SourceLoop adds that piece without rewriting any of your HTML. Three steps, around five minutes total, attribution flowing on every submission afterwards. ## What SourceLoop captures from HTML Forms Each HTML Forms submission lands in SourceLoop with this context: - **The visitor's acquisition channel** (organic, paid, referral, social, direct) - **UTM parameters** parsed from the landing URL - **Pages visited** in chronological order before the submission - **Time on site** ahead of the form fill - **Number of distinct sessions** before the lead converted - **Email + name** extracted from the form fields - **First-touch landing page** the visitor first arrived on - **Source attributed to the converting session** - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to add markup to ``) - An **HTML Forms form** active on a published WordPress page ## Step 1: Drop SourceLoop's snippet into WordPress Inside SourceLoop, head to **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to your WordPress ``. The typical paths: - A standalone header plugin (Insert Headers and Footers, WPCode) - Your active theme's `header.php` just before `` - Tag manager firing on All Pages - For developer-built themes, the `wp_head` hook is the right place Whichever path, the snippet must run on every page that hosts an HTML Forms form. ## Step 2: Confirm the form is on a tracked page HTML Forms doesn't require any per-form configuration to play nicely with SourceLoop. Once the snippet loads, every HTML Form on every page that includes the snippet is attributable. Quick sanity pass: - The form is on a **published** page or post - The form **includes an email field** (SourceLoop uses email to identify the lead) - Aggressive script-defer rules aren't reshuffling SourceLoop after your form's submit handler runs > **Submissions through unpublished pages can't be attributed** > Pages that haven't been published, draft posts, scheduled future posts, and password-protected pages typically don't serve the SourceLoop snippet to anonymous visitors. Submissions on those pages reach HTML Forms but won't have a marketing source in SourceLoop. ## Step 3: Run a verification submission Open your form's page in an **incognito tab**, append `?utm_source=test&utm_medium=verify&utm_campaign=htmlforms-check` to the URL, and submit a test entry with an email you control. Within seconds, the new lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values pinned to the record. ## Where to see HTML Forms submissions in SourceLoop ### Contacts Hub Every HTML Forms submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Drill into a contact for the visitor's complete pre-submission journey. ![SourceLoop Contacts Hub showing an HTML Forms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the broader read, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups HTML Forms submissions by source, medium, and campaign so you can see what's converting at a glance. ![SourceLoop attribution dashboard with HTML Forms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), configure a funnel ending in "HTML Forms submission". Cut by source, landing page, or device to find the highest-converting paths from first visit to form fill. ![SourceLoop funnel report ending in an HTML Forms submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is in your mix, forward HTML Forms submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real lead generation, not vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### HTML Forms is a free open-source plugin. Does that affect SourceLoop compatibility? No. SourceLoop tracks submissions in the browser, independent of how the form plugin is licensed or distributed. The free plugin works exactly the same as any commercial plugin. ### I built my form with raw HTML inside HTML Forms. Will it still get tracked? Yes. That's exactly the kind of form the plugin is built for, and SourceLoop attaches attribution to the submission regardless of how custom the HTML is, as long as the form posts back to the page on your site. ### My HTML Form submits to Mailchimp via the plugin's integration. Does the Mailchimp subscriber still get the source data? SourceLoop creates an attribution-rich contact record on its own side, independent of Mailchimp. If you want Mailchimp itself to receive UTM data, add hidden form fields for the UTM values and map them to Mailchimp merge fields, alongside (not instead of) SourceLoop. ### Can I track HTML Forms submissions inside a WordPress page builder (Gutenberg, Beaver, Bricks)? Yes. The page builder you use to place the form's shortcode is irrelevant. As long as the SourceLoop snippet loads in `` on the page, the submission gets attributed. ### Does this work if my form posts via AJAX vs full page reload? Yes. Both submission paths are captured the same way. --- # How to track lead source in JetFormBuilder Pair every JetFormBuilder submission with proper marketing attribution so each lead arrives with its source, campaign, and complete visitor journey. Source: https://sourceloop.ai/help/track-lead-source-in-jetformbuilder/ Updated: 2026-05-28 --- JetFormBuilder is the form layer of the Crocoblock Jet ecosystem, paired with JetEngine for dynamic content and Elementor / Gutenberg for the front-end, it's the form plugin many developer-friendly WordPress sites pick when they need more than a vanilla contact form. Marketing attribution isn't on its feature list. SourceLoop fills that role without changing anything inside JetFormBuilder. Three steps, under ten minutes total, with no JetFormBuilder configuration required. ## What SourceLoop captures from JetFormBuilder For every JetFormBuilder submission, SourceLoop attaches: - **Acquisition channel** of the visitor (organic, paid, social, referral) - **UTM parameter set** from the landing URL - **Page-by-page browsing path** before submission - **Time on site** ahead of the conversion - **Repeat-visit count** before the lead converted - **Email + name** read from the JetFormBuilder fields - **First-touch landing page** of the visitor's history - **Source of the converting session** specifically - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to edit `` markup) - A **JetFormBuilder** form embedded on a published WordPress page ## Step 1: Install SourceLoop's tracking snippet From SourceLoop, head to **Setup -> Tracking code** in the left sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Drop the snippet into the `` of your WordPress site: - Header-injection plugin (Insert Headers and Footers, WPCode) - Active theme's `header.php` immediately before `` - Crocoblock JetThemeCore has a header-script slot if you're using the Jet theme stack - Tag manager firing on All Pages The snippet must run on every page that hosts a JetFormBuilder form. ## Step 2: Confirm your form is on a published page JetFormBuilder doesn't need any per-form switch flipped. Once the snippet loads site-wide, every JetFormBuilder form on every published page is automatically attributable. Quick verification: - The form is on a **published** page or post - The form **collects an email address** (SourceLoop uses email as the lead key) - Crocoblock's performance optimisations aren't deferring SourceLoop past the form's submit handler > **JetFormBuilder forms on draft or private pages aren't attributable** > Drafts, password-protected pages, and unpublished posts typically don't expose the SourceLoop snippet to anonymous visitors. Submissions on those pages reach JetFormBuilder but show up in SourceLoop without a marketing source. ## Step 3: Submit a test entry Open your form's host page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=jetform-check` glued onto the URL. Submit a real entry using an email you control. Within seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see JetFormBuilder submissions in SourceLoop ### Contacts Hub Each JetFormBuilder submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact to see the visitor's complete pre-submission browsing timeline. ![SourceLoop Contacts Hub showing a JetFormBuilder submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups JetFormBuilder submissions by source, medium, and campaign so you can see at a glance which channels actually convert. ![SourceLoop attribution dashboard with JetFormBuilder submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "JetFormBuilder submission". Slice by source, content, or device to find your highest-converting routes. ![SourceLoop funnel report ending in a JetFormBuilder submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid campaigns, mirror JetFormBuilder submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the auction algorithms train on real form fills, not just clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the setup. ## Frequently Asked Questions ### Does this work with the free JetFormBuilder plugin or only Pro? Both. SourceLoop runs in the browser and doesn't depend on JetFormBuilder Pro add-ons or any specific Crocoblock feature gate. ### I use JetFormBuilder with JetEngine for custom post creation. Will lead attribution still be captured? Yes. JetEngine continues to handle the CRUD side, post creation, custom field updates, frontend manipulation, on its own. SourceLoop attaches attribution to the submission event in parallel, completely separate from the JetEngine workflow. ### Are JetFormBuilder's payment fields (Stripe, PayPal) compatible? Yes. Once the payment completes and JetFormBuilder records the submission, SourceLoop attaches attribution to the lead. The payment flow runs inside JetFormBuilder, unaffected. ### My JetFormBuilder form is inside an Elementor or Bricks page. Does that matter? No. The page builder you use to lay out the form is irrelevant. As long as the page loads the SourceLoop snippet, attribution attaches to the submission. ### Will my JetFormBuilder webhooks, MailChimp, and ActiveCampaign actions still fire? Yes. JetFormBuilder's post-submit actions continue to run normally. SourceLoop saves an attribution-rich copy of the lead on its end without interfering with your existing automations. --- # How to track lead source in Kali Forms Attach proper lead-source attribution to every Kali Forms submission so each entry tells you where the visitor came from, not just what they typed. Source: https://sourceloop.ai/help/track-lead-source-in-kali-forms/ Updated: 2026-05-28 --- Kali Forms is the WordPress form plugin that's punched well above its weight, polished drag-and-drop builder, generous free tier, and an AppSumo lifetime deal a lot of agencies still rely on. The marketing-attribution piece is what it doesn't cover. SourceLoop bolts that on, completely outside Kali Forms' own settings. Three steps, under ten minutes, attribution active on every form afterwards. ## What SourceLoop captures from Kali Forms Each Kali Forms submission lands in SourceLoop with this context: - **Acquisition source** (organic, paid, social, referral, direct) - **UTM parameter values** parsed from the landing URL - **Browsing path** captured in order before submission - **Time on site** ahead of the form fill - **Repeat-visit count** before they converted - **Email + name** read from your Kali Forms fields - **First-touch landing page** of the visitor's history - **Source attributed to the converting session** - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to add markup to ``) - A **Kali Forms** form embedded on a published WordPress page ## Step 1: Add SourceLoop's snippet to your WordPress site Inside SourceLoop, open **Setup -> Tracking code** in the left sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Place it in the `` of your WordPress site. Choose any one of: - A header-injection plugin (Insert Headers and Footers, WPCode) - Your active theme's `header.php`, just before `` - A "Custom Code" panel inside Yoast, Rank Math, or SEOPress - Google Tag Manager firing on All Pages The snippet must run on every page where a Kali Form might appear. ## Step 2: Confirm your form is on a tracked page No per-form switch to flip inside Kali Forms. Once the snippet loads site-wide, every Kali Form on every published page is attributable automatically. Worth checking: - The form is on a **published** page or post (not a draft) - The form **collects an email address**, SourceLoop uses email as the lead ID - Your caching/optimisation plugin isn't deferring SourceLoop past the form's submit > **Standalone preview URLs aren't attributable** > Any URL that doesn't actually load the SourceLoop snippet (a preview link, a staging subdomain you didn't set up, a private page) means submissions show up in Kali Forms but not in SourceLoop's attribution. Always route campaigns to a fully published page. ## Step 3: Send a test submission Open your Kali Form's page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=kali-check` glued to the URL. Submit a test entry using an email you control. Within seconds, the lead should land at the top of the **Contacts Hub** in SourceLoop with the three test UTM values attached to the record. ## Where to see Kali Forms submissions in SourceLoop ### Contacts Hub Every Kali Forms submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact to see the full pre-submission journey, every page they visited, every campaign that touched them. ![SourceLoop Contacts Hub showing a Kali Forms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the channel rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Kali Forms submissions by source, medium, and campaign so you can compare what's pulling weight. ![SourceLoop attribution dashboard with Kali Forms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Kali Forms submission". Slice by source, content, or device to find the routes that actually convert. ![SourceLoop funnel report ending in a Kali Forms submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is in the mix, forward Kali Forms submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real lead generation. [Connect your Google Ads account](/help/connect-google-ads/) walks through it. ## Frequently Asked Questions ### Does this work with the free Kali Forms plugin? Yes. SourceLoop runs in the browser and works the same whether you're on Kali Forms' free version or any of the paid tiers. ### I picked up Kali Forms through an AppSumo lifetime deal. Does that matter? Not at all. Tracking is browser-side and entirely outside Kali Forms' licensing model. ### Are Kali Forms' calculation, conditional logic, and multi-step features supported? Yes. All in-form behavior runs inside Kali Forms. SourceLoop only attaches marketing context to the final submission, so any combination of advanced features works the same. ### My Kali Forms forms connect to Mailchimp, PayPal, and Stripe. Will those integrations keep firing? Yes. Kali Forms continues to push submissions and payments through your configured integrations. SourceLoop records attribution in parallel on its own side, never touching the outbound flow. ### Can I track submissions from Kali Forms inside a popup builder (e.g., Elementor Popups)? Yes. The popup is just a different placement on the same page. As long as the page hosting the popup loads the SourceLoop snippet in ``, submissions get attributed. --- # How to track lead source in MetForm Tie every MetForm submission to the marketing channel that drove it so your Elementor forms finally tell you where the leads came from. Source: https://sourceloop.ai/help/track-lead-source-in-metform/ Updated: 2026-05-28 --- MetForm is the form add-on built specifically for Elementor, drag-and-drop inside Elementor's editor, with form-specific widgets, conditional logic, and a clean Pro upgrade path. The piece it leaves out (because Elementor doesn't cover it either) is marketing attribution. SourceLoop slots in cleanly to add it. Three steps, around five minutes, no MetForm-side configuration needed. ## What SourceLoop captures from MetForm For each MetForm submission, SourceLoop attaches: - **Visitor's acquisition channel** (organic, paid, social, referral) - **UTM parameter set** from the landing URL - **Pages visited** in chronological order before submission - **Time on site** ahead of the form fill - **Number of distinct sessions** before conversion - **Email + name** from the MetForm fields - **First-touch landing page** at the start of the visitor's history - **Source of the converting session** specifically - **Device type, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to edit `` markup) - A **MetForm** form embedded inside an Elementor page or template on a published URL ## Step 1: Install the SourceLoop snippet on your WordPress site Inside SourceLoop, head to **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to your WordPress ``. Common paths: - A standalone header plugin (Insert Headers and Footers, WPCode) - Elementor's own "Custom Code" feature (Templates -> Custom Code -> Add New, Location = ``) - Your active theme's `header.php` just before `` - Google Tag Manager firing on All Pages The snippet must run on every page that hosts a MetForm form (or popup containing a MetForm). ## Step 2: Confirm the form is on a published Elementor page There's no per-form switch to flip in MetForm. Once the snippet is loading site-wide, every MetForm form inside any published Elementor page is attributable. A quick checklist: - The Elementor page or template is **published**, not a draft - The MetForm form **collects an email address**, used by SourceLoop as the lead key - Elementor's "Custom Code" deferral isn't pushing SourceLoop to fire after the form submits > **Elementor Preview URLs and draft pages aren't attributable** > Sharing an Elementor preview URL or any page that hasn't been published doesn't expose the SourceLoop snippet to public visitors. Submissions on those pages reach MetForm but show up without a marketing source. ## Step 3: Submit a test entry Open the Elementor page hosting your MetForm in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=metform-check` appended to the URL. Submit a test entry using an email you can access. Within seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values pinned to the record. ## Where to see MetForm submissions in SourceLoop ### Contacts Hub Every MetForm submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact to see the visitor's complete pre-submission browsing path. ![SourceLoop Contacts Hub showing a MetForm submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the campaign-level read, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls MetForm submissions up by source, medium, and campaign. ![SourceLoop attribution dashboard with MetForm submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) with "MetForm submission" as the goal step. Cut by source, content, or device to find the most-converting routes. ![SourceLoop funnel report ending in a MetForm submission conversion step](/help/screenshots/sourceloop-funnel.png) If your stack includes paid acquisition, forward MetForm submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn from real form fills. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work on the free MetForm plugin or do I need MetForm Pro? Both work. SourceLoop's tracking is browser-side and doesn't depend on MetForm Pro features. Free MetForm users get the same attribution coverage. ### I built my MetForm form inside Elementor. Will Elementor's caching or its own analytics conflict? No. SourceLoop and Elementor operate on different layers, SourceLoop on visitor sessions, Elementor on layout and design. The two don't overlap and won't conflict. ### My MetForm uses Conditional Logic and Multi-Step features. Are all completed submissions tracked? Yes. MetForm's conditional logic, multi-step navigation, and dynamic fields all execute inside the form. SourceLoop captures the final submission, so any flow that reaches that final submit gets attributed. ### Will MetForm's MailChimp, ActiveCampaign, and Webhook integrations keep firing as configured? Yes. Your MetForm integrations continue to push submissions to their destinations. SourceLoop saves an attribution-rich copy of the lead independently on its own side. ### Can I track MetForm submissions inside an Elementor Popup? Yes. A popup is just a placement variant on the same page. As long as the page that hosts the popup loads SourceLoop in ``, submissions get attributed. --- # How to track lead source in MightyForms Add marketing attribution to every MightyForms submission so each lead arrives with the source, campaign, and journey behind the form fill. Source: https://sourceloop.ai/help/track-lead-source-in-mightyforms/ Updated: 2026-05-28 --- MightyForms is the newer-generation drag-and-drop form builder that's been carving out share by leaning hard on UX and pricing. It does the form part well but, like every form tool, leaves marketers without the answer to the most basic question, which campaign produced each lead. SourceLoop solves that with a single snippet. Three steps, about five minutes, attribution active on every form afterwards. ## What SourceLoop captures from MightyForms Each MightyForms submission flows into SourceLoop with this context wrapped around it: - **Visitor's acquisition channel** (organic, paid, social, referral, direct) - **Complete UTM stack** from the landing URL - **Sequence of pages browsed** before the submission - **Time on site** before the form fill - **Number of distinct sessions** before conversion - **Email + name** read from the MightyForms fields - **First-touch landing page** of the visitor's history - **Source of the converting session** specifically - **Device type, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the page where you embed your MightyForms form - A **MightyForms** form published and ready to embed via the script snippet ## Step 1: Install SourceLoop's snippet on your site From SourceLoop, head to **Setup -> Tracking code** in the sidebar and copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet into your site's ``. Quick ways: - WordPress: a header-injection plugin or your theme's `header.php` - Webflow: Project Settings -> Custom Code -> Head Code - Framer: Site Settings -> General -> Custom Code -> Start of head - Shopify: Online Store -> Themes -> Edit code -> `theme.liquid` - Tag manager: an All Pages tag The snippet should run on every page where a MightyForms form might appear. ## Step 2: Embed your MightyForms form on a tracked page In MightyForms, open your form, click **Publish -> Embed**, and copy the script embed snippet. Paste it into the page where the form should appear. That page also needs the SourceLoop snippet from step 1. > **MightyForms-hosted form URLs aren't attributable** > A direct MightyForms-hosted form link routes visitors to MightyForms' domain, bypassing your site entirely, so SourceLoop never sees the source. Always send campaign traffic to a page on your own domain that embeds the form. ## Step 3: Run a verification submission Visit your form's page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=mighty-check` glued to the URL. Submit a real entry using an email you can check. Within seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see MightyForms submissions in SourceLoop ### Contacts Hub Each MightyForms submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click a row to see the visitor's complete pre-submission browsing path. ![SourceLoop Contacts Hub showing a MightyForms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups MightyForms submissions by source, medium, and campaign. Quick read on which channels are actually growing your list. ![SourceLoop attribution dashboard with MightyForms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "MightyForms submission". Slice by source, content, or device to find your highest-converting paths. ![SourceLoop funnel report ending in a MightyForms submission conversion step](/help/screenshots/sourceloop-funnel.png) If your strategy includes paid acquisition, forward MightyForms submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real lead generation, not vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work on MightyForms' free plan? Yes. SourceLoop's tracking is browser-side and plan-agnostic on MightyForms' side. Free, Starter, Pro, every tier works the same way. ### Are MightyForms' payment forms (Stripe, PayPal) compatible with attribution? Yes. Once the payment is complete and MightyForms records the submission, SourceLoop attaches the visitor's marketing source to the lead. The payment flow itself runs inside MightyForms and isn't affected. ### My MightyForms form is on a popup-based embed. Does that still get tracked? Yes. Popup, slider, and inline embed types all fire submissions the same way once the visitor completes the form on your page. ### Will my MightyForms Zapier, MailChimp, and webhook integrations still fire? Yes. MightyForms continues to deliver submissions to every connected destination. SourceLoop captures attribution independently in its own backend. ### Can I track MightyForms submissions on a multi-language site? Yes. Place the SourceLoop snippet in the `` of every language variant of your site, and form fills from any version get attributed normally. ### Does this conflict with MightyForms' built-in spam protection? No. Spam protection runs entirely inside MightyForms. SourceLoop only attaches attribution to submissions that complete successfully, which is what you want. --- # How to track lead source in Quill Forms Match every Quill Forms response with the marketing source and journey behind it. Conversational forms with the attribution context they were missing. Source: https://sourceloop.ai/help/track-lead-source-in-quill-forms/ Updated: 2026-05-28 --- Quill Forms is the WordPress form plugin built explicitly as a Typeform alternative, conversational layouts, generous free tier, polished feel, and a growing add-on library. The thing missing from the equation, like every form plugin, is marketing attribution. SourceLoop adds that without changing any of your Quill setup. Three steps, under ten minutes, attribution flowing on every submission afterwards. ## What SourceLoop captures from Quill Forms For each Quill Forms submission, SourceLoop attaches: - **Acquisition channel** of the visitor (organic, paid, referral, social, direct) - **Full UTM parameter set** from the landing URL - **Pages visited** in order before the submission - **Time on site** before the form fill - **Number of distinct sessions** preceding conversion - **Email + name** captured from the Quill Forms fields - **First-touch landing page** of the visitor's history - **Source attributed to the converting session** - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to add markup to ``) - A **Quill Forms** form embedded on a published WordPress page ## Step 1: Install SourceLoop's snippet on your WordPress site From SourceLoop, click **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to the `` of your WordPress site: - A header-injection plugin (Insert Headers and Footers, WPCode) - Your active theme's `header.php` immediately before `` - A "Custom Code" panel inside any SEO plugin - Google Tag Manager firing on All Pages Make sure the snippet loads on every page where a Quill Form might appear. ## Step 2: Confirm the form is on a published page Quill Forms doesn't require any per-form switch to integrate with SourceLoop. Once the snippet loads, every Quill Form on every published page that includes the snippet becomes attributable. A quick verification pass: - The Quill Forms form is on a **published** page or post - The form **collects an email**, used by SourceLoop as the lead key - Aggressive script-defer rules aren't reshuffling SourceLoop after the form's submit handler > **Submissions on draft / private pages can't be attributed** > Draft posts, scheduled future posts, and private pages typically don't expose the SourceLoop snippet to anonymous visitors. Submissions on those pages reach Quill Forms but show up without a marketing source in SourceLoop. ## Step 3: Run a verification submission Open your Quill Forms page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=quill-check` glued to the URL. Submit a real entry using an email you control. Within seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values pinned to the record. ## Where to see Quill Forms submissions in SourceLoop ### Contacts Hub Every Quill Forms response becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Drill into a contact to see the visitor's full pre-submission browsing path. ![SourceLoop Contacts Hub showing a Quill Forms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the channel rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Quill Forms submissions by source, medium, and campaign so you can quickly see what's converting. ![SourceLoop attribution dashboard with Quill Forms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) with "Quill Forms submission" as the final step. Slice by source, content, or device to find the highest-converting routes. ![SourceLoop funnel report ending in a Quill Forms submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid campaigns, mirror Quill Forms submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the auction algorithms train on real form fills. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Does this work with the free Quill Forms plugin? Yes. Tracking runs in the browser and works the same on Quill Forms' free version as on any Pro tier. ### Quill Forms is built as a Typeform-style conversational experience. Does that affect tracking? No. Whichever Quill Forms layout the visitor sees, classic or one-question-at-a-time, the final submission event carries the attribution data exactly the same. ### I use Quill Forms with payment fields (Stripe). Are paid submissions tracked? Yes. Once the payment completes and Quill Forms records the submission, SourceLoop attaches attribution to the lead. The payment flow itself runs inside Quill Forms, untouched. ### My Quill Forms send data to Mailchimp, ActiveCampaign, and Slack. Does any of that change? No. Your existing Quill Forms integrations continue to fire as configured. SourceLoop captures attribution independently on its end without disrupting any outbound flow. ### Can SourceLoop track multi-step Quill Forms with conditional logic? Yes. The number of steps and the logic between them runs entirely inside Quill Forms. SourceLoop only attaches attribution to the final submission, so all flow variants get captured the same way. --- # How to track lead source in SureForms Tie every SureForms submission to the campaign, channel, and journey behind it so each lead arrives with full marketing context, not just answers. Source: https://sourceloop.ai/help/track-lead-source-in-sureforms/ Updated: 2026-05-28 --- SureForms is the newest piece of the Brainstorm Force toolkit, the same team behind Astra, Spectra, and SureCart, and it's built block-first to fit naturally inside Gutenberg-native sites. What it doesn't handle (yet) is marketing attribution. SourceLoop slots in cleanly to add that layer. Three steps, around five minutes, no SureForms changes required. ## What SourceLoop captures from SureForms For each SureForms submission, SourceLoop attaches: - **Acquisition channel** of the visitor (organic, paid, social, referral, direct) - **UTM parameters** parsed from the landing URL - **Page-by-page browsing path** before submission - **Cumulative time on site** ahead of the form fill - **Number of distinct sessions** before they converted - **Email + name** from the SureForms fields - **First-touch landing page** at the start of the journey - **Source of the converting session** specifically - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to edit `` markup) - A **SureForms** form embedded on a published WordPress page ## Step 1: Drop SourceLoop's snippet into your site Inside SourceLoop, open **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to the `` of your WordPress site: - The Astra theme's "Header Scripts" option (Customizer -> General -> Custom Layout -> Header) is the cleanest path if you're on Astra - Standalone plugins like Insert Headers and Footers or WPCode - Your active theme's `header.php` just before `` - Google Tag Manager firing on All Pages The snippet must run on every page that hosts a SureForms form. ## Step 2: Confirm the form is on a published page No per-form switch to flip in SureForms. Once the snippet is loading site-wide, every SureForms form on every published page becomes attributable. Worth verifying: - The form is on a **published** page or post (not a draft) - The form **collects an email field**, used by SourceLoop as the lead key - Aggressive performance plugins aren't deferring SourceLoop after the form's submit handler runs > **Submissions through unpublished pages aren't attributable** > Drafts, scheduled future posts, and password-protected pages typically don't serve the SourceLoop snippet to anonymous visitors. Submissions on those pages reach SureForms but won't have a marketing source in SourceLoop. ## Step 3: Run a test submission Visit your SureForms page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=sureforms-check` appended to the URL. Submit a test entry using an email you can check. Within seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values pinned to the record. ## Where to see SureForms submissions in SourceLoop ### Contacts Hub Each SureForms submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact for the visitor's complete pre-submission browsing timeline. ![SourceLoop Contacts Hub showing a SureForms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the channel rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups SureForms submissions by source, medium, and campaign so you can read what's converting at a glance. ![SourceLoop attribution dashboard with SureForms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "SureForms submission". Slice by source, content, or device to find the highest-converting routes from first visit to form fill. ![SourceLoop funnel report ending in a SureForms submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is part of your strategy, mirror SureForms submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on actual lead generation. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with the free SureForms plugin? Yes. SourceLoop's tracking is browser-side and works the same whether you're on free SureForms or SureForms Pro. ### SureForms is built block-first in Gutenberg. Does that matter? No. SourceLoop tracks the submission event, regardless of whether the form was built with blocks, shortcodes, or a builder. Block-based forms work the same way as any other. ### I'm also using Astra theme and Spectra blocks. Any conflict? No. The Brainstorm Force stack (Astra, Spectra, SureCart, SureForms) is fully compatible. SourceLoop's snippet just needs to be in the page ``, which Astra's "Header Scripts" option supports natively. ### Will my SureForms ActiveCampaign, MailerLite, and webhook integrations keep firing? Yes. SureForms continues to push submissions to every connected destination. SourceLoop captures attribution in parallel without intercepting any of your outbound flows. ### Can I track SureForms used on a SureCart checkout or upsell flow? Yes. As long as the page hosting the form (whether it's a SureCart checkout, upsell page, or standalone landing page) loads the SourceLoop snippet, submissions get attributed normally. --- # How to track lead source in WeForms Stamp every WeForms submission with the marketing source, campaign, and visitor journey so you can see exactly which channel produced each lead. Source: https://sourceloop.ai/help/track-lead-source-in-weforms/ Updated: 2026-05-28 --- WeForms is the WordPress form plugin from weDevs, the same team behind Dokan and ERP, which means it's particularly well-suited to multivendor stores, vendor onboarding, and HR-style workflows. The marketing piece is what it doesn't try to do. SourceLoop adds attribution to every submission with no WeForms-side changes. Three steps, under ten minutes, attribution flowing on every form fill afterwards. ## What SourceLoop captures from WeForms For each WeForms submission, SourceLoop attaches: - **The visitor's acquisition channel** (organic, paid, referral, social, direct) - **Full UTM stack** parsed from the landing URL - **Pages visited** in order before the form fill - **Time on site** ahead of the submission - **Number of distinct sessions** before conversion - **Email + name** from the WeForms fields - **First-touch landing page** of the visitor's history - **Source attributed to the converting session** - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to edit `` markup) - A **WeForms** form embedded on a published WordPress page ## Step 1: Install SourceLoop's snippet on WordPress Inside SourceLoop, open **Setup -> Tracking code** in the left sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to the `` of your WordPress site: - A header-injection plugin like Insert Headers and Footers or WPCode - Your active theme's `header.php`, just before `` - The "Custom Code" panel in any SEO plugin - Google Tag Manager firing on All Pages The snippet should run on every page where a WeForms form might appear. ## Step 2: Confirm the form is on a published page There's no per-form switch to flip in WeForms. Once the snippet loads site-wide, every WeForms form on every published page is attributable automatically. Quick sanity pass: - The form is on a **published** page or post (not a draft) - The form **collects an email address**, used by SourceLoop to identify the lead - Performance plugins aren't deferring SourceLoop past the form's submit handler > **Unpublished or private pages can't be attributed** > Drafts, scheduled future posts, and password-protected pages typically don't expose the SourceLoop snippet to anonymous visitors. WeForms still records the submission but SourceLoop won't have a marketing source to attach. ## Step 3: Send a verification submission Open the form's page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=weforms-check` glued to the URL. Submit a real entry using an email you can check. Within seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see WeForms submissions in SourceLoop ### Contacts Hub Every WeForms submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact to see the visitor's complete pre-submission browsing path. ![SourceLoop Contacts Hub showing a WeForms submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups WeForms submissions by source, medium, and campaign so you can quickly see what's converting. ![SourceLoop attribution dashboard with WeForms submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "WeForms submission". Slice by source, content, or device to find the routes that actually convert. ![SourceLoop funnel report ending in a WeForms submission conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition, forward WeForms submissions to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real form fills, not vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with the free WeForms plugin? Yes. SourceLoop's tracking is browser-side and works the same on the free plugin as on any WeForms Pro tier. ### I run a multivendor marketplace built on Dokan with WeForms for vendor signups. Does that get attributed? Yes. The vendor-signup submission is the conversion event, and SourceLoop attaches attribution to the lead exactly the same way as a standard contact form fill, complete with the visitor's pre-signup browsing journey. ### My WeForms forms have multi-step pages and conditional logic. Anything different? No. WeForms' in-form behavior runs entirely inside the plugin. SourceLoop only captures the final submission, so multi-step flows behave the same as single-page forms. ### Will my WeForms MailChimp, ActiveCampaign, and webhook integrations still fire? Yes. WeForms continues to forward submissions to every connected destination. SourceLoop saves an attribution-rich copy of the lead independently, no overlap. ### Can SourceLoop track WeForms used in a popup or modal? Yes. Popup, modal, and inline placements all fire the submission event the same way. As long as the host page loads the SourceLoop snippet, attribution attaches. --- # How to track lead source in WS Form Equip every WS Form submission with the marketing context behind it, source, campaign, landing page, and full visitor journey, all attached automatically. Source: https://sourceloop.ai/help/track-lead-source-in-ws-form/ Updated: 2026-05-28 --- WS Form is the WordPress form plugin developers and agencies reach for when they want serious control, advanced layout, deep API surface, action chains, and complex conditional logic. It does almost everything a form needs to do. The one thing it doesn't is tell you which marketing channel produced each submission. SourceLoop covers that. Three steps, around five minutes, no WS Form-side configuration needed. ## What SourceLoop captures from WS Form For each WS Form submission, SourceLoop attaches: - **The marketing channel** that delivered the visitor - **UTM parameter values** parsed from the landing URL - **Sequence of pages browsed** before the form submission - **Time on site** ahead of the conversion - **Number of distinct sessions** before they finally submitted - **Email + name** read from the WS Form fields - **First-touch landing page** of the visitor's history - **Source attributed to the converting session** specifically - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **WordPress admin** access (or another way to add markup to ``) - A **WS Form** form embedded on a published WordPress page ## Step 1: Install the SourceLoop snippet on your site Inside SourceLoop, head to **Setup -> Tracking code** in the left sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to your WordPress ``. Common ways: - A header-injection plugin like Insert Headers and Footers or WPCode - Your active theme's `header.php`, just before `` - For developer-built themes, hook it via the `wp_head` action - Tag manager firing on All Pages The snippet must run on every page that hosts a WS Form form. ## Step 2: Confirm the form is on a tracked page WS Form doesn't require any per-form switch. Once the snippet is loading site-wide, every WS Form on every published page that includes the snippet is attributable. Verify these basics: - The form is embedded on a **published** page or post (not a draft) - The form **collects an email**, used by SourceLoop as the lead identifier - Aggressive performance plugins or theme-level deferring isn't pushing SourceLoop after WS Form's submit handler > **Server-to-server submissions don't carry attribution** > WS Form supports REST API and webhook-driven submissions where there's no browser visitor at all. Those submissions don't have a journey to attach, by design. Browser-based form fills (the normal case) are what gets attributed. ## Step 3: Run a verification submission Open your WS Form page in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=wsform-check` glued onto the URL. Submit a real entry using an email you control. Within seconds, the lead should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see WS Form submissions in SourceLoop ### Contacts Hub Every WS Form submission becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact for the visitor's full pre-submission browsing timeline. ![SourceLoop Contacts Hub showing a WS Form submission with the lead's full journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the campaign-level view, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls WS Form submissions up by source, medium, and campaign so you can see which channels are pulling weight. ![SourceLoop attribution dashboard with WS Form submissions grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) with "WS Form submission" as the final step. Slice by source, content, or device to find the highest-converting paths. ![SourceLoop funnel report ending in a WS Form submission conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is in the mix, mirror WS Form submissions back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real form fills instead of clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with WS Form Lite (free)? Yes. SourceLoop's tracking is browser-side and works the same on WS Form Lite as it does on WS Form Pro Edition, with no licence-tier dependencies. ### WS Form's advanced layout uses CSS Grid and Flexbox. Will the snippet break anything visually? No. The SourceLoop snippet doesn't render any UI or inject any CSS, so WS Form's layout (no matter how complex) is unaffected. ### I use WS Form's "Action" system to push data to webhooks and integrations. Will those keep firing? Yes. WS Form's actions continue to execute as configured. SourceLoop attaches attribution data on its end in parallel, completely separate from your action chain. ### My WS Form forms include calculations, conditional logic, and repeaters. Are submissions still tracked? Yes. None of WS Form's advanced field types or logic affect tracking. SourceLoop only fires on the final submission, so any complexity inside the form happens entirely upstream. ### WS Form supports REST API submissions and headless workflows. Is that compatible with SourceLoop? SourceLoop captures submissions made through a tracked page in a real browser. Pure REST API submissions (server-to-server, no visitor session) don't create attribution data, by design, since there's no visitor journey to attach. --- # How to track lead source in Calendly Capture the lead source, campaign, and full journey behind every Calendly booking so you can tie pipeline back to the channel that drove it. Source: https://sourceloop.ai/help/track-lead-source-in-calendly/ Updated: 2026-05-28 --- If you use Calendly to book meetings and you want to know **which marketing channel drove each booking**, this guide walks you through the full setup. The whole thing takes about 5 minutes and works on every Calendly plan. Once configured, every meeting booked through Calendly shows up in SourceLoop with the original source, campaign, and complete pre-booking journey, so you can tie pipeline back to the ad, post, or email that actually drove it. ## What SourceLoop captures from Calendly For every booking, you'll see: - **First-touch source** (e.g., `google / cpc`, `linkedin / organic`) - **Last-touch source** before the booking - **UTM parameters** from the original landing session: `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term` - **Landing page** and full referrer chain - **First seen** and last seen timestamps - **Full journey** (every page the visitor viewed before booking) - **Booking details** (invitee name and email, fetched via the Calendly API) - **Device, location, and browser** All of this lands on the **Contacts Hub** as a new lead, where you can expand any row to see the complete journey timeline. ## Before you start You'll need: - A **SourceLoop account** ([sign up free](https://app.sourceloop.ai/sign-up) if you don't have one) - **Admin access** to the site Calendly is embedded on (or where the booking link is shared) - A **Calendly account** (any plan, including Free) ## Step 1: Install the SourceLoop tracking script 1. Log in to SourceLoop. 2. In the sidebar, go to **Setup -> Tracking code**. 3. Copy the tracking script shown on that page. ![SourceLoop Setup page showing the tracking code script ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) 4. Paste the script inside the `` tag of every page on your site, including the page where your Calendly link or embed lives. Once installed, SourceLoop starts capturing visitor source and journey data automatically. The next steps link those visits to the actual Calendly bookings. ## Step 2: Get your Calendly API token Calendly doesn't expose the invitee's name and email in its public booking events, so SourceLoop needs a token to call the API and fetch those details. Here's how to generate one: 1. Log in to [Calendly](https://calendly.com/). 2. Click **Integrations and apps** in the left sidebar, then search for **API & Webhooks**. ![Calendly Integrations and apps page with the API and Webhooks search result highlighted](/help/screenshots/xrhspjaoka.png) 3. Click **Generate New Token** (or copy your existing one if you already have one). ![Calendly Personal Access Tokens screen with the Generate New Token button highlighted](/help/screenshots/kc109y5emk.png) 4. Copy the token. Keep this tab open, you'll paste it into SourceLoop in the next step. ## Step 3: Connect Calendly in SourceLoop 1. Back in SourceLoop, go to **Setup -> Meeting**. 2. Click **Calendly** in the list of meeting tools. ![SourceLoop Setup page on the Meeting tab with Calendly highlighted](/help/screenshots/navigate-to-calendly-from-setup-page.png) 3. A drawer opens on the right with a tab to enter your Calendly API key. Paste the **personal access token** you copied in the previous step. 4. Click **Save**. ![SourceLoop Calendly drawer with the API key field and Save button](/help/screenshots/enter-calendly-api-key.png) From this point on, whenever someone books a meeting through Calendly, SourceLoop will: 1. Detect the booking from your tracking pixel (or from the Calendly API if the link was shared off-site) 2. Call the Calendly API in the background to fetch the invitee's name and email 3. Stitch the booking onto the visitor's existing journey 4. Surface it as a new lead on the **Contacts Hub** within a few seconds ## Step 4: Verify it's working Book a test meeting on yourself: open your site in an **incognito window**, navigate to the page with the Calendly link, append `?utm_source=test&utm_campaign=verify` to the URL, then book a meeting using a real email address. Within ~10 seconds, the booking should appear on the **Contacts Hub** with the test UTMs attached. > **Not seeing the booking?** > Open the page with `?sl_debug=1` to see SourceLoop's event log in the browser console. If the booking event is firing but the lead isn't appearing, double-check that the Calendly API token in SourceLoop is correct, and that it has access to the calendar you're using. ## Where to see Calendly bookings Once the integration is live, every booking shows up in three places inside SourceLoop: ### Contacts Hub Every booking is a new contact row, view them at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click any row to expand the complete pre-booking journey, every page the visitor viewed, every UTM they touched, and every previous session leading up to the booking. ![SourceLoop Contacts Hub showing a lead's full journey timeline with sources, sessions, and the Calendly booking event](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Open the traffic dashboard at [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to see Calendly bookings counted as conversions, broken down by source, campaign, channel, and landing page. Compare paid vs. organic, identify your best-performing campaigns, and see which content drives meetings. ![SourceLoop attribution dashboard with bookings grouped by source, campaign, and landing page](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Head to [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) and add "Calendly booking" as a step in any funnel to measure conversion rate from any campaign, ad set, or page to a booked meeting, then slice by source to see which channel converts best. ![SourceLoop funnel report with Calendly booking as a conversion step](/help/screenshots/sourceloop-funnel.png) You can also push Calendly bookings as **offline conversions** to Google Ads, Meta, and LinkedIn so their bidding algorithms can optimize for real meetings instead of vanity form fills. See [Connect your Google Ads account](/help/connect-google-ads/) for the offline-conversions setup. ## Common issues > **Bookings appear with no name or email** > This means SourceLoop captured the booking event but couldn't reach the Calendly API. Recheck the API token in **Integrations -> API Keys**. If the token is correct, make sure the Calendly account that generated the token has access to the event type the meeting was booked on. > **Source shows as 'Direct' for every booking** > This usually means the Calendly link was shared off-site (email signature, DM, etc.) and the visitor never landed on a tracked page. To attribute these bookings, drive your Calendly traffic through a landing page that has the SourceLoop pixel installed, and tag the page URL with UTMs. That's it. From here, every meeting Calendly books is automatically tied back to the campaign that drove it. ## Frequently Asked Questions ### Do I need a paid Calendly plan? No. The personal-access token used here is available on every Calendly plan, including Free. ### Does this work for embedded Calendly widgets too? Yes. As long as the SourceLoop tracking pixel is installed on the page where the embed lives, bookings made through the embed are captured the same way. ### Will SourceLoop see bookings that come from links shared in email or DMs (not from my site)? Those bookings will still be captured because of the API token, but they won't have web journey data attached, since the visitor never landed on a page with the pixel. They'll show up as "Direct" source. ### Where do the booking name and email come from? Calendly's API. SourceLoop calls the API in the background using your token, fetches the invitee's details, and stitches them onto the attribution record automatically. ### Is my Calendly API token safe? Yes. SourceLoop encrypts API tokens at rest. They're only used server-side to fetch booking details and are never exposed in the browser. --- # How to track lead source in Cal.com Tie every Cal.com booking back to the campaign, channel, or content that drove it, with the full pre-booking journey attached to each invitee. Source: https://sourceloop.ai/help/track-lead-source-in-cal-com/ Updated: 2026-05-28 --- Cal.com is the open-source scheduler of choice for technical teams, developer tools, and SaaS founders, and most people who book through it are coming from a specific content piece, ad, or campaign. This guide gets that source data stitched onto every booking, so you can see exactly which channel sends meetings instead of guessing. The setup is short, you'll be done in under five minutes, and it works on the free Cal.com tier. ## What SourceLoop captures from Cal.com After setup, each Cal.com booking lands in SourceLoop with: - **Origin channel** plus `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, and `utm_term` from the visitor's first session - **Last channel touched** in the session that actually produced the booking - **Every page visited** between the first session and the booking, in order - **Landing page and referrer** from the original visit - **Session count** before the visitor finally booked (great for measuring intent) - **Days between first visit and booking** so you can see your real sales cycle length - **Invitee email** pulled from the Cal.com booking event in the browser - **Device, country, and browser** so you can spot which segments convert best ## Before you start Make sure you have: - A **SourceLoop workspace** ([free trial here](https://app.sourceloop.ai/sign-up) if you don't have one) - **Edit access** to your website's HTML, or to the tag manager that injects scripts - A **Cal.com account** with at least one event type set up (free or paid) ## Step 1: Install the SourceLoop tracking script Open SourceLoop, head to **Setup -> Tracking code** in the left sidebar, and copy the snippet shown on that page. ![SourceLoop Setup page with the tracking code script ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Drop the snippet inside the `` of every page on your site. Particularly important: the page that hosts your Cal.com embed or links to your `cal.com/` booking page. If you use Google Tag Manager, paste the snippet into a Custom HTML tag set to "All Pages". ## Step 2: Embed Cal.com on your booking page SourceLoop captures Cal.com bookings made on a page where the tracking script is already running. Use whichever Cal.com embed style fits your site: - **Inline embed**: Cal.com -> **Event Type -> Embed -> Inline** -> paste the snippet on your page - **Floating popup button**: same menu, "Floating popup" tab -> paste the script - **Element click**: trigger Cal.com from any existing button or link on your page All three behave the same for attribution. There's nothing else to configure on the SourceLoop side. As soon as a visitor books, the lead and their full pre-booking journey show up in your dashboard. > **Off-site Cal.com links can't be tracked** > SourceLoop captures Cal.com bookings only when the widget loads on a page that has the SourceLoop script. Bookings made through a raw `cal.com/` link shared in email, Slack, calendar invites, or DMs **won't appear in SourceLoop at all**, because the visitor never touches a tracked page and there's no API key for SourceLoop to fall back on. Route your Cal.com campaigns through a landing page on your own domain that embeds the widget. ## Step 3: Verify it's working Open your site in an **incognito window**, navigate to the page with the Cal.com embed, and add `?utm_source=test&utm_campaign=cal-verify` to the URL. Book a test slot using a real email you control. Within a few seconds, the booking should appear in **Contacts Hub** with the test UTMs attached. If you don't see it, append `?sl_debug=1` to your page URL to enable SourceLoop's diagnostic console output, useful for spotting setup issues quickly. ## Where to see Cal.com bookings Once it's live, every Cal.com booking shows up in three places inside SourceLoop: ### Contacts Hub Visit [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) to see every Cal.com booking as a row in the Contacts Hub. Expand any row and you get the visitor's full pre-booking timeline, what brought them in, which posts they read, and when they came back to actually book. ![SourceLoop Contacts Hub showing a lead's full journey timeline with sources, sessions, and the Cal.com booking event](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Pull up the traffic dashboard at [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic). Bookings made through Cal.com flow into the conversions count, sliced by source, campaign, channel, and landing page. Useful to answer questions like "is paid LinkedIn pulling its weight against organic search?" ![SourceLoop attribution dashboard with Cal.com bookings grouped by source, campaign, and landing page](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), drop a "Cal.com booking" step into any funnel definition. Then break the funnel down by `utm_source` or landing page to see which acquisition channel actually converts visitors into booked meetings, not just clicks. ![SourceLoop funnel report with Cal.com booking as a conversion step](/help/screenshots/sourceloop-funnel.png) You can also forward Cal.com bookings as **offline conversions** to Google Ads, Meta, and LinkedIn so their bidding algorithms learn from real booked meetings, not just form fills. See [Connect your Google Ads account](/help/connect-google-ads/) for the offline-conversions wiring. That's it, Cal.com bookings are now tied back to whatever channel actually produced them. ## Frequently Asked Questions ### Does this work with self-hosted Cal.com? Yes. SourceLoop works directly through the Cal.com embed on your page, so self-hosted instances are treated exactly the same as the hosted version. ### Do I need a paid Cal.com plan? No. Embed widgets and inline links are available on the free Cal.com tier, which is everything SourceLoop needs. ### What if visitors book via my Cal.com link shared in an email or LinkedIn DM? Those bookings won't appear in SourceLoop at all. Because Cal.com integrates via embed (no API key), SourceLoop only sees bookings made on a page that has the tracking script. To attribute traffic from emails or social, point those campaigns at a landing page on your own domain that embeds the Cal.com widget. ### Do I need to connect Cal.com's API? No. There's no OAuth flow, no API key to manage, no webhook setup. SourceLoop captures Cal.com bookings purely from the embed on your page, with zero configuration on the Cal.com side. ### Does this work with Cal.com routing forms? Yes, as long as the routing form is embedded on a page where the SourceLoop script is installed. The form fill plus the resulting booking are both captured. --- # How to track lead source in GoHighLevel Calendar See exactly which marketing channel sourced every appointment booked through your GoHighLevel calendar, ad, organic, email, or referral. Source: https://sourceloop.ai/help/track-lead-source-in-gohighlevel-calendar/ Updated: 2026-05-28 --- GoHighLevel is the platform of choice for agencies, coaches, and local-business operators, and meeting-booking is one of its most-used features. The problem most users hit: GoHighLevel can tell you that an appointment was booked, but it can't tell you which Facebook ad, Google search, or blog post got that prospect to your booking page in the first place. This guide closes that loop. You'll be done in about five minutes, no API connection or OAuth required. ## What SourceLoop captures from GoHighLevel Every appointment booked through your embedded GHL calendar arrives in SourceLoop with: - **Acquisition channel** and the full UTM set (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`) from the visitor's first session - **Last channel touched** in the session that produced the appointment - **Every page the visitor browsed** on your site before booking, in chronological order - **Original landing page and referring URL** - **Session count** before the prospect finally booked (high count usually means a longer, content-driven journey) - **Time elapsed** between first visit and the booked appointment - **Contact email and name** captured from the booking form - **Device, country, and browser** so you can see how mobile vs desktop perform ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where your GoHighLevel calendar widget is embedded - An active **GoHighLevel sub-account** with at least one calendar set up ## Step 1: Install the SourceLoop tracking script Inside SourceLoop, click **Setup** in the left navigation and select the **Tracking code** tab. Copy the snippet shown there. ![SourceLoop Setup page showing the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add the snippet to the `` of every page on your site, including (and especially) the pages where you embed your GoHighLevel calendar. If you manage multiple client sites, repeat this for each: each website should have its own SourceLoop workspace and its own snippet. ## Step 2: Embed the GoHighLevel calendar on your booking page In GoHighLevel, open **Calendars -> your calendar -> Settings -> Form & Payment -> Calendar Embed**. Grab either the iframe embed code or the booking widget link, then drop it onto your landing page. Common embed patterns inside GoHighLevel: - **Inline iframe**: pastes a full calendar view directly on your page - **Popup widget**: opens the calendar as an overlay when a visitor clicks a CTA - **Funnels & Websites**: drop the Calendar element directly into a GoHighLevel funnel page Whichever you choose, the rule is the same, the page that displays the calendar must have the SourceLoop snippet in its ``. Once that's true, every appointment booked through the widget will flow into SourceLoop automatically, with the lead's full marketing journey attached. > **Off-platform booking links aren't trackable** > If you share your raw GoHighLevel booking link directly through SMS blasts, email campaigns, or DMs (skipping your website entirely), those appointments won't show up in SourceLoop. The visitor never lands on a page that has the tracker, and the integration has no off-site path to fall back on. Always route campaigns through a landing page that embeds the calendar. ## Step 3: Verify it's working Open your booking page in an **incognito window**, add `?utm_source=test&utm_medium=verify&utm_campaign=ghl-check` to the URL, and book a test appointment with a real email you control. Inside SourceLoop, head to the **Contacts Hub**. The test appointment should appear within seconds, with the test UTM values visible on the lead record. ## Where to see GoHighLevel appointments After setup, every GoHighLevel booking lands in three places inside SourceLoop: ### Contacts Hub Head to [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) to see each appointment as a contact row. Expanding a row reveals the entire timeline: the ad that brought them in, the case studies they read, the pricing page visits, and finally the booking. Useful for both qualification calls and post-mortem analysis. ![SourceLoop Contacts Hub showing a GoHighLevel appointment lead with their full pre-booking journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Open [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to see your GoHighLevel appointments grouped by source, medium, and campaign. Especially valuable for agencies managing paid spend, you can see which Facebook ad set or Google campaign drove the most booked calls per dollar. ![SourceLoop attribution dashboard with GoHighLevel appointments grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "GoHighLevel appointment". Layer it by `utm_source` or by landing page to see exactly where you're losing prospects on the path to a booked call, and where you're converting them. ![SourceLoop funnel report ending in a GoHighLevel appointment conversion step](/help/screenshots/sourceloop-funnel.png) You can also push your GoHighLevel appointments back to **Google Ads, Meta, and LinkedIn as offline conversions**, so the bidding algorithms optimize for booked meetings instead of vanity clicks. See [Connect your Google Ads account](/help/connect-google-ads/) to wire that up. ## Common issues > **Tracking only works for one client's site** > If you're an agency managing multiple GoHighLevel sub-accounts, each client's website needs its own SourceLoop workspace and its own tracking snippet. Reusing a single workspace's snippet across multiple client sites will jumble attribution data across accounts, which makes the reports unreliable. That's the full setup. Every GoHighLevel appointment is now tied to the marketing channel that actually drove it, so you can stop guessing what's working. ## Frequently Asked Questions ### Does this work for sub-account calendars in GoHighLevel? Yes. Whatever sub-account is hosting the calendar widget is irrelevant to SourceLoop, what matters is that the SourceLoop script is on the page where the widget is embedded. Each client website you manage needs its own SourceLoop workspace and tracking script. ### I use a white-label or custom subdomain for my GHL booking widget. Will tracking still work? Yes. Custom domains have no effect on attribution. Just make sure the SourceLoop script is in the `` of whatever page hosts the widget, whether that page lives on `bookings.youragency.com`, a client site, or anywhere else. ### Do I need to connect GoHighLevel's API or OAuth? No. There's no API connection to set up. The integration is fully driven by embedding the GHL calendar widget on a page that already has SourceLoop installed. ### My clients send the GHL booking link directly via SMS or email. Will those bookings show up? No. Bookings made through a raw GoHighLevel booking link (without first visiting a page that has the SourceLoop tracker) won't appear in SourceLoop. To attribute SMS or email campaigns, point them at a landing page on your domain that embeds the calendar. ### Can I track bookings across multiple client sub-accounts at once? Each client should have their own SourceLoop workspace. Mix tracking across clients in a single workspace would muddy the attribution data. Use a workspace per client (or per agency-owned property). --- # How to track lead source in HubSpot Meetings Add real attribution to every meeting booked through your HubSpot Meetings link, so your reps know which campaign or content actually drove the call. Source: https://sourceloop.ai/help/track-lead-source-in-hubspot-meetings/ Updated: 2026-05-28 --- HubSpot Meetings is the default scheduler for sales teams already on HubSpot CRM. It does its job well, the meeting books, the contact is created, the rep gets the calendar invite. What it doesn't tell you is **which marketing investment actually produced that meeting**. SourceLoop adds that missing layer. The setup runs three short steps and works on every HubSpot tier including Free. ## What SourceLoop captures from HubSpot Meetings Every meeting booked through your embedded HubSpot scheduler flows into SourceLoop with: - **Lead's first acquisition channel** plus the original `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, and `utm_term` values - **Email and name** as entered on the HubSpot meeting form - **Conversion path**, the ordered list of every page the prospect visited before the meeting was booked - **Time-to-meeting**: how long elapsed between the visitor's first touch and the booked appointment - **Page count** in the pre-meeting journey - **Original landing page and referrer** - **Last session source** before the meeting was booked (useful for picking apart top-of-funnel vs. closing channels) - **Device, country, and browser** for segment-level insights ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where you'll embed the HubSpot meeting link - A **HubSpot account** (any tier) with at least one meeting link configured under **Sales -> Meetings** ## Step 1: Install the SourceLoop tracking script From your SourceLoop dashboard, open **Setup** in the left navigation and switch to the **Tracking code** tab. Copy the snippet you see there. ![SourceLoop Setup page showing the tracking code snippet to copy](/help/screenshots/sourceloop-tracking-code-script.png) Drop the snippet inside the `` of every page on your site. The most important page to cover is the one where prospects book meetings, but installing it site-wide gives SourceLoop the full picture of every visitor's journey before they finally book. ## Step 2: Embed your HubSpot meeting link on the booking page In HubSpot, navigate to **Sales -> Meetings -> your meeting link -> Embed**. HubSpot offers two embed options: - **Inline iframe**: shows the full HubSpot meeting widget directly on your page - **Popup link**: opens the meeting widget in a modal when a visitor clicks a CTA Either option works for attribution. The non-negotiable bit is that the page hosting the embed has the SourceLoop snippet from step 1. Once both pieces are in place, meetings booked through the widget are tied back to whatever marketing channel originally brought the visitor to your site, no extra setup needed. > **Direct meetings.hubspot.com links aren't attributable** > Bookings made through a raw `meetings.hubspot.com/` link shared in email signatures, calendar invites, LinkedIn DMs, or anywhere else off your website **won't appear in SourceLoop**. There's no path for SourceLoop to see those visits, because they never touch a tracked page. Route campaigns through a landing page that embeds the meeting widget to get full attribution. ## Step 3: Verify it's working Open the booking page in an **incognito window**, add `?utm_source=test&utm_medium=verify&utm_campaign=hubspot-check` to the URL, and book a test meeting using a real email you control. Switch over to SourceLoop's **Contacts Hub**, the test meeting should appear in a few seconds with the test UTM values attached. If it doesn't, append `?sl_debug=1` to the booking page URL to enable diagnostic output in the browser console. ## Where to see HubSpot meetings in SourceLoop After the integration is live, every meeting booked through the HubSpot widget shows up across three SourceLoop surfaces: ### Contacts Hub Each HubSpot meeting becomes a row in the Contacts Hub at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click into any row to reveal the prospect's complete pre-meeting timeline: the ad they clicked, the blog posts they read, the case study they downloaded, and finally the meeting booking. Great prep material before the call. ![SourceLoop Contacts Hub showing a HubSpot meeting lead with their full pre-meeting journey timeline](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Pull up [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to see HubSpot meetings rolled up by source, campaign, channel, and landing page. Particularly useful for sales leaders: see which marketing investment is producing the most meetings per dollar, then double down. ![SourceLoop attribution dashboard showing HubSpot meetings rolled up by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Inside [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel that ends in a "HubSpot meeting" step. Break it down by source or by landing page to find which channels create high-intent traffic and which ones drive impressive click counts but never produce a sales call. ![SourceLoop funnel report ending in a HubSpot meeting conversion step](/help/screenshots/sourceloop-funnel.png) Want to close the loop further? Push the HubSpot meeting data back to **Google Ads, Meta, and LinkedIn as offline conversions** so the ad platforms learn to bid for booked meetings, not just form completions. See [Connect your Google Ads account](/help/connect-google-ads/) for the offline-conversion setup. That's the full picture, HubSpot meetings are now tied to the marketing channels that actually source them. ## Frequently Asked Questions ### Does this work with the free HubSpot CRM plan? Yes. HubSpot's Meetings tool is included on the free plan, and SourceLoop captures bookings made through any embedded meeting link regardless of HubSpot tier. ### Will SourceLoop overwrite the source fields HubSpot already sets on a contact? No. SourceLoop keeps its attribution data on its own contact record and dashboards. HubSpot's `hs_analytics_source` and similar properties stay untouched. If you'd like that data flowing back into HubSpot contact records, you can wire it up via the HubSpot CRM sync. ### Does this support round-robin meetings and group calendars? Yes. The attribution capture happens at the embed level, so it works the same whether the booking lands on a single rep, a round-robin pool, or a group calendar. ### I share my meeting link in my email signature. Will those bookings be tracked? No. Bookings made through a raw `meetings.hubspot.com/` URL skip your website entirely, so SourceLoop never sees the visit and can't attribute it. If email-signature meetings matter to you, point the link to a landing page that embeds the meeting widget instead. ### Can I push HubSpot meeting attribution into Google Ads or Meta as offline conversions? Yes. Once SourceLoop is capturing the meetings, you can forward them as offline conversions to any ad platform you've connected, so the algorithms can optimize for booked meetings instead of form fills. --- # How to track lead source in TidyCal Pin every TidyCal booking to the marketing channel that produced it so you can stop guessing which content, ad, or referral actually drives your calls. Source: https://sourceloop.ai/help/track-lead-source-in-tidycal/ Updated: 2026-05-28 --- TidyCal is the scheduler of choice for solopreneurs, consultants, and lifetime-deal collectors who want Calendly's experience without the recurring bill. The trade-off most users don't notice until later: when bookings start coming in, there's no clean way to see which marketing effort actually produced them. This guide fixes that. Five minutes start to finish, no API keys, works on every TidyCal plan. ## What SourceLoop captures from TidyCal After setup, every TidyCal booking arrives in SourceLoop tagged with: - **Acquisition source** plus the full UTM set (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`) from the visitor's first session - **Conversion sequence**: chronological list of every page they viewed leading up to the booking - **Days from first visit to booked call**, useful for understanding your content-to-call timeline - **Number of return visits** before the prospect finally booked - **Contact email** captured from the TidyCal booking form - **First-seen landing page** and the referring URL - **Most recent session source** right before the booking happened - **Device, location, and browser** so you can spot patterns by segment ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where your TidyCal widget will be embedded - A **TidyCal account** with at least one booking type configured ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, click into **Setup** from the left menu, and select the **Tracking code** tab. Grab the snippet displayed there. ![SourceLoop Setup page showing the tracking snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet inside the `` of your site, the page that hosts the TidyCal embed especially, but installing it site-wide gives you a full picture of every visitor's journey before they finally book. ## Step 2: Embed your TidyCal booking widget In TidyCal, open your booking type and head to the **Embed** tab. TidyCal gives you a few placement options: - **Inline iframe**: drops the full TidyCal calendar directly onto your page - **Popup button**: opens TidyCal in a modal when a visitor clicks a CTA - **WordPress block**: if your site runs on WordPress, the official TidyCal block embeds it natively All three feed SourceLoop equally well. The only rule: the page that displays the embed must also have the SourceLoop snippet from step 1. Once both are in place, every booking through the widget shows up in SourceLoop with the full marketing journey attached. > **Direct TidyCal links can't be attributed** > Bookings made through a raw `tidycal.com/` link, the kind you'd post in a Twitter bio, paste into a DM, or include in an email signature, **won't appear in SourceLoop**. The visitor jumps straight to TidyCal without touching a tracked page, so there's nothing for SourceLoop to attribute. Route those campaigns through a landing page that embeds the widget if you want the data. ## Step 3: Verify it's working Open your booking page in an **incognito tab**, add `?utm_source=test&utm_medium=verify&utm_campaign=tidycal-check` to the URL, and book a test slot with a real email address you control. Pop over to the **Contacts Hub** in SourceLoop, the test booking should show up within seconds, with the test UTM values attached to the contact record. ## Where to see TidyCal bookings Once everything's wired, your TidyCal bookings show up across three different SourceLoop views, each useful for a different question: ### Contacts Hub Open [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) and you'll find every TidyCal booking as a contact row. Click into any row to unfold the full pre-booking story, what brought them in, what they read on the way, and how many sessions it took before they were ready to schedule. ![SourceLoop Contacts Hub showing a TidyCal booking with the lead's full pre-booking journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard Switch to [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) to see TidyCal bookings broken down by source, medium, and campaign. The view answers questions like "is my newsletter actually driving calls, or just opens?" and "which podcast guest spot delivered the most bookings?" ![SourceLoop attribution dashboard with TidyCal bookings grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel in [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in a "TidyCal booking" step. Slice it by source or by landing page to see your highest-converting paths, the ones where visitors actually become booked calls instead of bouncing. ![SourceLoop funnel report ending in a TidyCal booking conversion step](/help/screenshots/sourceloop-funnel.png) If you're running paid acquisition, you can also forward the TidyCal bookings to **Google Ads, Meta, and LinkedIn as offline conversions** so the ad platforms can optimize against real booked calls rather than form fills. [Connect your Google Ads account](/help/connect-google-ads/) walks through that setup. ## Common issues > **Bookings missing only from specific pages** > If TidyCal bookings show up from some pages but not others, the SourceLoop snippet probably isn't installed sitewide. Visit the page where bookings aren't tracking, view source, and confirm the SourceLoop snippet is in the ``. The easiest fix is to add it to your site's global head template (or via a tag manager) so every page is covered. ## Frequently Asked Questions ### Does this work with the TidyCal lifetime deal plan? Yes. Whatever TidyCal plan you have (Free, Lifetime, or Pro), the attribution flow is identical. Setup happens entirely on your website with no TidyCal-side configuration. ### What about TidyCal's WordPress plugin? Works fine. As long as the SourceLoop snippet is also loaded on the WordPress page where the TidyCal block lives, bookings made through it are captured normally. ### I post my TidyCal link in my Twitter bio and Instagram profile. Will those bookings be attributed? No. Bookings made directly through a `tidycal.com/` link won't show up in SourceLoop, because the visitor never lands on a page that has the tracker. To attribute social profile traffic, point your bio links to a landing page that embeds the TidyCal widget instead. ### Does this support TidyCal's group bookings and paid bookings? Yes. Group sessions, paid bookings, and standard 1-on-1 meetings all run through the same embed code, so they all get the same attribution treatment. ### Can I attribute past TidyCal bookings retroactively? No. SourceLoop only captures bookings made after the tracking script is installed and the embed is live on a tracked page. Historical bookings can't be backfilled. --- # How to track lead source in Zoom Scheduler Find out which channel, campaign, or content drives every meeting booked through Zoom Scheduler, with the full visitor journey on each invitee. Source: https://sourceloop.ai/help/track-lead-source-in-zoom-scheduler/ Updated: 2026-05-28 --- Zoom Scheduler quietly turned Zoom into a serious Calendly competitor in 2023, and a lot of teams have moved to it for the obvious reason: it's bundled with the Zoom subscription you're already paying for. The catch is the same one every meeting tool has, Zoom can tell you a meeting was booked, but not which Google ad, blog post, or email campaign produced it. This guide closes that gap. Setup runs about five minutes. No API access, no developer apps to register, no extra cost on top of your Zoom plan. ## What SourceLoop captures from Zoom Scheduler Every meeting booked through your embedded Zoom Scheduler lands in SourceLoop with: - **Original referral source** plus the full UTM set from the visitor's first session - **Pre-meeting touchpoints**: the ordered list of every page the prospect viewed leading up to the booking - **Total time spent on your site** before they finally hit "book" - **Visit recurrence**: number of return sessions before the conversion - **Email captured from the Zoom Scheduler form** - **Inbound channel of the final session** (which often differs from the first-touch source) - **Origin landing page and referring URL** - **Device, country, and browser** for segment-level analysis ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the Zoom Scheduler embed will live - A **Zoom paid plan** with Scheduler enabled and at least one booking type set up ## Step 1: Install the SourceLoop tracking script In SourceLoop, navigate to **Setup** via the left-hand menu and open the **Tracking code** tab. Copy the snippet displayed. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste the snippet into the `` of every page on your site, especially the page that hosts your Zoom Scheduler embed. Site-wide installation is recommended so SourceLoop builds the complete pre-booking journey for each visitor. ## Step 2: Embed Zoom Scheduler on your booking page Inside Zoom, head to **Scheduler -> your booking type -> Customize -> Embed**. Zoom offers two embed flavors: - **Inline embed**: the Zoom Scheduler renders directly inside your page - **Pop-out link**: opens Scheduler as an overlay when a visitor clicks a button or link Either one works the same way for attribution. The constraint is the same as ever, the page that displays the embed must have the SourceLoop snippet in its ``. Once both are in place, meetings booked through the Scheduler are tied back to the marketing channel that originally drove the visit. > **Direct zoom.us/scheduler links can't be attributed** > Visitors who jump straight to your `zoom.us/scheduler/...` URL without first hitting a page on your site **won't appear in SourceLoop**. Without a tracked visit on the way in, there's nothing for SourceLoop to attribute. Route Zoom Scheduler campaigns (email signatures, social bios, calendar invites) through a landing page that embeds the Scheduler to fix this. ## Step 3: Verify it's working Open your booking page in an **incognito tab**, append `?utm_source=test&utm_medium=verify&utm_campaign=zoom-check` to the URL, and book a test slot with a real email you control. Check the **Contacts Hub** in SourceLoop, the test meeting should appear in a few seconds with the test UTMs attached. If something isn't right, append `?sl_debug=1` to the page URL to enable diagnostic console output. ## Where to see Zoom Scheduler bookings Three SourceLoop surfaces give you complementary views of your Zoom Scheduler data: ### Contacts Hub Go to [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) and each Zoom Scheduler booking shows up as a contact row. Expand a row to see the entire pre-meeting timeline, useful prep before hopping on the actual Zoom call. ![SourceLoop Contacts Hub showing a Zoom Scheduler booking with the prospect's full pre-meeting timeline](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard The traffic dashboard at [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Zoom Scheduler bookings by source, medium, and campaign. It's how you spot patterns like "our LinkedIn organic posts produce more booked meetings than the entire paid Facebook campaign." ![SourceLoop attribution dashboard with Zoom Scheduler bookings broken down by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Open [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) and configure a funnel ending in "Zoom Scheduler booking". Cut it by source or landing page to find where prospects either turn into booked meetings or drop off. ![SourceLoop funnel report ending in a Zoom Scheduler booking conversion step](/help/screenshots/sourceloop-funnel.png) When paid acquisition is part of the picture, you can also push Zoom Scheduler bookings as **offline conversions** to Google Ads, Meta, and LinkedIn so the bidding algorithms learn from real booked calls instead of vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the offline-conversion wiring. That wraps Zoom Scheduler, every booking is now tied to whatever channel actually produced it. ## Frequently Asked Questions ### Do I need a paid Zoom plan for this to work? Zoom Scheduler itself requires a Zoom paid plan (Pro and above), but that's a Zoom limitation, not a SourceLoop one. Once Scheduler is available in your account, SourceLoop tracks it on every plan including Basic SourceLoop subscriptions. ### Does this work with Zoom's team scheduling and round-robin features? Yes. Attribution is captured at the embed level, so single-user, team, and round-robin schedulers all flow into SourceLoop the same way. ### My organization runs Zoom for Government / dedicated tenant. Will that work? Yes, as long as the Scheduler embed loads on a page where the SourceLoop script is also installed. The integration doesn't depend on Zoom's standard domain or any region-specific endpoint. ### I include my Zoom Scheduler link in my email signature. Will those bookings be tracked? No. Bookings made through a raw `zoom.us/scheduler/...` URL bypass your website entirely, so SourceLoop never sees those visitors and has no way to attribute them. Embed the scheduler on a landing page if you want signature traffic in your reports. ### Does this require any Zoom API access or developer setup? No. There's no OAuth, no API key, no developer app to register on the Zoom side. Setup happens entirely on your website with the SourceLoop tracking script and the Zoom Scheduler embed. --- # How to track lead source in YouCanBookMe Connect every YouCanBookMe booking to the channel, content, or campaign that actually produced it, with the visitor's full pre-booking journey attached. Source: https://sourceloop.ai/help/track-lead-source-in-youcanbookme/ Updated: 2026-05-28 --- YouCanBookMe is the booking tool of choice for coaches, educators, consultants, and small-business owners who want straightforward per-calendar pricing without the enterprise overhead. The blind spot most users hit: YouCanBookMe shows you when a booking happens, but not what brought the prospect to the calendar in the first place. This guide adds that layer. The setup is four steps, around ten minutes start to finish. The webhook step requires YouCanBookMe Professional or Teams. ## What SourceLoop captures from YouCanBookMe After setup, each YouCanBookMe booking lands in SourceLoop alongside: - **Marketing channel origin** plus the full UTM set captured on the visitor's first session - **Page sequence visited** before the booking, in chronological order - **Days since first visit** at the moment the booking was made - **Session count** prior to the conversion (handy for spotting nurture-heavy paths) - **Contact email, first name, last name, and phone** pulled from the YouCanBookMe booking form - **Direct entry page** and the referring URL from the original session - **Most recent session source** right before the booking - **Device, location, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website where the YouCanBookMe widget will be embedded - A **YouCanBookMe account on the Professional or Teams plan** (Free and Personal plans don't include webhooks, which this guide depends on) - At least one booking page set up in YouCanBookMe ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup** from the left navigation, and click the **Tracking code** tab. Copy the snippet you see. ![SourceLoop Setup page with the tracking code snippet to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add the snippet to the `` of every page on your site. Site-wide installation gives SourceLoop the full picture of each visitor's journey before they land on the page where you embed YouCanBookMe. ## Step 2: Add a hidden question to your YouCanBookMe form YouCanBookMe uses **Hidden Questions** with **shorthand codes** to capture URL parameters and attach them to bookings. Add one that SourceLoop can use to identify each visitor. 1. In YouCanBookMe, open your booking page and click **Booking Form** in the left sidebar. 2. Click **Add Question**. ![YouCanBookMe Booking Form sidebar with the Add Question button highlighted](/help/screenshots/0m5e58lz8xdo.png) 3. Choose **Hidden Question** as the question type. ![YouCanBookMe question type picker with Hidden Question selected](/help/screenshots/oaffgqg19e.png) 4. Set the **Shorthand** to exactly: ``` SL_AID ``` 5. Save the form. > **Shorthand must be UPPERCASE** > YouCanBookMe is case-sensitive on shorthand codes. `sl_aid` or `Sl_Aid` will be silently dropped and bookings will reach SourceLoop without an identifier to match against. Use `SL_AID` exactly as shown above. That's the only field you need to add. SourceLoop fills in the channel, source, journey, and other attribution details on its side using the value of this one identifier. ## Step 3: Embed your booking page on your site YouCanBookMe ships several embed flavors, any of them work: 1. In YouCanBookMe, click the **three-dot icon** on your booking page and choose **Share**. ![YouCanBookMe booking page menu with Share highlighted](/help/screenshots/zon94o6xymd.png) 2. Pick the embed style that fits your site, **inline**, **button popup**, or **text-link popup**. ![YouCanBookMe Share dialog showing the three embed types](/help/screenshots/iilb2m7gpnf.png) 3. Copy the embed code and paste it onto the page on your site where you want bookings to happen. > **External youcanbook.me links can't be attributed** > A booking made through a raw `.youcanbook.me` link, the kind you'd paste into client emails, share in a Slack DM, or include in a calendar invite, **won't show up in SourceLoop**. The visitor never lands on a tracked page, so there's no session for SourceLoop to attribute. Route those campaigns through a landing page that embeds the booking widget instead. ## Step 4: Configure the YouCanBookMe webhook The webhook is what delivers each new booking from YouCanBookMe to SourceLoop, where it gets matched to the visitor's journey and turned into a fully-attributed lead. 1. In SourceLoop, go to **Setup -> Incoming Webhooks** and copy your webhook URL. ![SourceLoop Setup page on the Incoming Webhooks tab with the webhook URL ready to copy](/help/screenshots/pxojz6z3zw.png) 2. In YouCanBookMe, open your booking page and go to **Integrations -> Webhooks**. 3. Click **+ Add Webhook**. 4. Configure it as follows: - **URL**: the SourceLoop webhook URL from step 1 - **Method**: `POST` - **Content Type**: `application/json` 5. For the request **Body**, paste this JSON template: ```json { "sl_aid": "{SL_AID}", "email": "{EMAIL}", "first_name": "{FNAME}", "last_name": "{LNAME}", "phone": "{PHONE}", "scheduled_date": "{START_DATE_TIME}", "meeting_title": "{TYPE-NAME}", "booking_id": "{BOOKING-ID}" } ``` YouCanBookMe interpolates each `{SHORTHAND}` placeholder with the actual value from the booking. The Hidden Question shorthand you set up in step 2 (`{SL_AID}`) works the same way as the built-in ones. 6. Set **Trigger** to **Booked**. 7. Save. Submit a test booking through your embedded page to confirm the conversion appears in SourceLoop as a Meeting with source, channel, and landing page populated. ## Where to see YouCanBookMe bookings Once the integration is live, three SourceLoop views give you complementary cuts of the same data: ### Contacts Hub Visit [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) and every YouCanBookMe booking appears as a contact row. Click into a row to unpack the prospect's full pre-booking arc, the search term, the blog post, the case study, and finally the calendar booking. ![SourceLoop Contacts Hub with a YouCanBookMe booking expanded to show the lead's full pre-booking journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard The traffic dashboard at [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) rolls up your YouCanBookMe bookings by source, medium, and campaign. Useful when you need to defend a marketing spend, the dashboard shows exactly which channels translate into real bookings versus which only deliver traffic. ![SourceLoop attribution dashboard with YouCanBookMe bookings grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Inside [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), configure a funnel ending in a "YouCanBookMe booking" step. Slice it by `utm_source` or landing page to see your most-efficient paths from first visit to booked call. ![SourceLoop funnel report ending in a YouCanBookMe booking conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition is part of your mix, you can also push YouCanBookMe bookings as **offline conversions** to Google Ads, Meta, and LinkedIn so the bidding algorithms optimize for booked meetings instead of clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers that setup. ## Frequently Asked Questions ### Does this require a paid YouCanBookMe plan? The hidden-question and embed steps work on every YouCanBookMe plan including Free, but the webhook step in this guide requires the Professional or Teams plan. Without webhooks enabled, bookings won't reach SourceLoop and attribution can't be stitched. ### Why does the Hidden Question shorthand need to be UPPERCASE? YouCanBookMe is case-sensitive on shorthand codes and silently drops lowercase or mixed-case variants. Set the shorthand exactly to `SL_AID` (all uppercase, with the underscore) so the URL parameter is captured correctly. ### I have a team YouCanBookMe account with several calendars. Do I need to set this up for each one? The SourceLoop tracking script only needs to be installed once on your site. The Hidden Question and webhook, however, are configured per booking page, so each calendar your team books on needs its own setup. ### I share my YouCanBookMe link in client emails. Will those bookings be captured? No. When prospects click a `.youcanbook.me` link directly, they reach YouCanBookMe without first visiting your website, so there's no journey data for SourceLoop to attach. Embed the booking page on a landing page if you want those bookings tracked. ### Does this support YouCanBookMe's group bookings? Yes. Group sessions, one-on-one bookings, and multi-host appointments all flow into SourceLoop the same way, as long as the embed lives on a tracked page and the webhook is configured. --- # How to track lead source in Zoho Bookings See exactly which marketing investment produces each Zoho Bookings appointment, with the full visitor journey saved next to every booking record. Source: https://sourceloop.ai/help/track-lead-source-in-zoho-bookings/ Updated: 2026-05-28 --- If your business runs on Zoho, the Bookings module is probably already part of your stack, no extra contracts, no separate tool to manage. The piece it doesn't solve is **attribution**: Zoho Bookings captures the appointment, but not the channel that produced it. This guide adds that context to every booking. Four steps, about ten minutes total. The webhook step requires Zoho Bookings on the Premium plan or above. ## What SourceLoop captures from Zoho Bookings Every appointment booked through your embedded Zoho Bookings widget reaches SourceLoop with: - **Inbound source** plus the complete UTM parameter set captured on the visitor's first session - **Visited pages**, in chronological order, leading up to the appointment - **Span of the customer journey**: total time elapsed between first touch and the actual booking - **Repeat visit count** before the conversion finally happened - **Email address** captured directly from the Zoho Bookings form - **Initial landing destination** and the URL that referred the visitor - **Source of the closing session**, the one that ended in the booking - **Device class, country, and browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the website (or Zoho Sites page) where the Zoho Bookings widget will live - A **Zoho Bookings account on the Premium plan or above** (Free and Basic don't include webhook integrations, which this guide requires) - At least one service / appointment type configured in Zoho Bookings ## Step 1: Install the SourceLoop tracking script Log in to SourceLoop, navigate to **Setup** in the left menu, and switch to the **Tracking code** tab. Copy the snippet you see. ![SourceLoop Setup page showing the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add the snippet to the `` section of every page on your site. Whether your booking page sits on Zoho Sites, WordPress, Webflow, or a custom-coded landing page, the SourceLoop snippet needs to load on it for attribution to work. ## Step 2: Add a hidden custom field to your booking form Zoho Bookings captures URL parameter values onto a booking only when there's a matching custom field on the booking form. Add the one SourceLoop needs. 1. In Zoho Bookings, open **Workspaces -> Services -> [your service] -> Booking Form**. 2. Click **+ Add Field** and choose **Single Line**. 3. Set the **Field Name** exactly to: ``` sl_aid ``` 4. Mark the field as **Hidden** so visitors don't see it on the booking form. 5. Save the booking form. > **Field name must be exactly sl_aid** > Zoho Bookings matches the incoming URL parameter to your custom field by name. Use lowercase only, no spaces, no extra punctuation. Different casing or naming will cause the value to be dropped silently. **Quick verify**: open your Zoho Bookings booking page URL with `?sl_aid=test123` appended and book a test slot. In the booking detail view, the **Questions** tab should show `sl_aid = test123`. If you see the value there, the field is wired correctly. ## Step 3: Embed the booking page on your site The booking widget needs to live on a page where the SourceLoop tracking script is already running. 1. In Zoho Bookings, go to **Share -> Embed**. 2. Pick the embed style you want, **Inline**, **Popup**, or **Floating button**. All three work the same way for attribution. 3. Copy the embed code and paste it onto the page on your site where bookings should happen. > **Direct zoho bookings URLs aren't attributable** > Appointments booked through a direct `bookings.zoho.com/...` link or your standalone Zoho Bookings page **won't appear in SourceLoop**. The visitor never touches a tracked page, so the attribution data has no path to attach to the booking. Funnel those campaigns through a landing page on your site that embeds the widget. ## Step 4: Configure the Zoho Bookings webhook The webhook is what delivers each new booking to SourceLoop, where the booking gets matched to the visitor's journey and saved as a fully-attributed lead. 1. In SourceLoop, go to **Setup -> Incoming Webhooks** and copy your webhook URL. 2. In Zoho Bookings, go to **Settings -> Integrations -> Webhooks**. 3. Click **+ Create Webhook**. 4. Configure the webhook with: - **URL**: the SourceLoop webhook URL from step 1 - **Event**: `Booking Created` (optionally also `Rescheduled` and `Cancelled` if you want those tracked too) - **Content Type**: `application/json` - **Method**: `POST` 5. Save. Book a test slot through your embedded page to confirm the appointment appears in SourceLoop with channel, source, and landing page populated. ## Where to see Zoho Bookings appointments Three SourceLoop views give you complementary perspectives on the same data: ### Contacts Hub Visit [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) to see every Zoho Bookings appointment as a contact row. Expand a row to reveal the visitor's full pre-booking timeline, useful as call prep when you want to know what content the prospect already consumed before walking into the meeting. ![SourceLoop Contacts Hub with a Zoho Bookings appointment expanded to show the lead's full pre-booking journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups your Zoho Bookings appointments by source, medium, and campaign. The view answers "which channel drives our highest-converting appointments?" in a single glance. ![SourceLoop attribution dashboard with Zoho Bookings appointments grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel that ends in "Zoho Bookings appointment". Slice it by source or landing page to find the paths that consistently turn into booked appointments versus the ones that look busy but never produce a meeting. ![SourceLoop funnel report ending in a Zoho Bookings appointment conversion step](/help/screenshots/sourceloop-funnel.png) When paid spend is in play, forward Zoho Bookings appointments to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms can optimize against real booked meetings. [Connect your Google Ads account](/help/connect-google-ads/) covers the offline-conversion setup. ## Frequently Asked Questions ### Does this work with Zoho One subscribers? Yes. Whether you reach Zoho Bookings through Zoho One, Zoho Workplace, or as a standalone subscription, the setup steps are identical. The Bookings module behaves the same across all entry points. ### Why does the custom field have to be named exactly "sl_aid"? Zoho Bookings matches the URL parameter to a custom field by name. The field name you create in the booking form has to exactly match the parameter the SourceLoop tracker sends, lowercase, no spaces, no underscores out of place. ### I host my Zoho Bookings page through Zoho Sites. Will attribution still work? Yes, on any page where you can install the SourceLoop tracking script and embed the booking widget. That includes Zoho Sites pages, your own WordPress site, a custom-built landing page, anywhere you control the HTML and the Zoho Bookings embed sits inside it. ### Visitors sometimes book through my direct Zoho Bookings page URL. Will those appointments show up? No. Bookings made through a raw `bookings.zoho.com/...` link won't appear in SourceLoop, because the visitor never lands on a tracked page first. To attribute that traffic, point your campaigns at a landing page that embeds the widget. ### Do I need a paid Zoho Bookings plan? The hidden-field and embed steps work on every Zoho Bookings tier, but the webhook step requires the Premium plan or above. Free and Basic plans don't include webhook integrations, so bookings can't reach SourceLoop. --- # How to track lead source in CallRail Turn every CallRail phone call into an attributed lead. Connect with an API key and each call lands with source, campaign, UTMs, and journey. Source: https://sourceloop.ai/help/track-lead-source-in-callrail/ Updated: 2026-05-31 --- When a phone call closes a deal, the obvious question is the one most teams can't answer: which ad, search, or page actually made the phone ring? CallRail tells you a call happened. It rarely tells you which marketing channel earned it. SourceLoop fills that gap. Connect CallRail once with an API key, and every call to a tracking number arrives in SourceLoop as a lead with its first-touch and last-touch source, the UTMs, the landing page, and the caller's entire browsing path before they dialed. The same attribution your form and chat conversions already carry, now on your phone calls. Four steps, about five minutes. Works on any paid CallRail plan. ## What SourceLoop captures from CallRail For every call to a CallRail tracking number, SourceLoop records a lead with **Type: Call** that carries: - **First-touch source** of the visitor (e.g. `google / cpc`, `bing / organic`) - **Last-touch source** of the session that led to the call - **UTM parameters** from the landing URL (source, medium, campaign, content, term) - **The full pre-call journey**: every page viewed and every ad clicked before the phone rang, when the caller browsed your site first - **Caller details**: phone number, name, and city or state when CallRail has them - **Call facts**: duration, direction, and whether it was answered - **Recording and transcription links**, when those are enabled on your CallRail account - **Tags and qualification** you apply to the call inside CallRail ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - The **SourceLoop tracker installed** on the pages where your tracked phone numbers appear, so there's a browsing session to attach each call to - A **CallRail account** with at least one tracking number live on your site - Permission to **create an API key in CallRail** (an admin role, typically) - **Admin** or **Owner** role in SourceLoop ## Step 1: Install the SourceLoop tracking script Attribution for a call is built from the web session that came before it, so the tracker has to be live on the pages where your CallRail numbers show up. If you've already installed it for form or chat tracking, you're set, skip to Step 2. If not, add the SourceLoop snippet to every page of your site (the global header is the simplest place). Full walkthrough: [Install the SourceLoop tracking pixel](/help/install-the-tracking-pixel/). > **Why the tracker matters for calls** > CallRail swaps in a unique tracking number per visitor and remembers the session behind it. The SourceLoop tracker is what gives that session its source, campaign, and journey in the first place. No tracker on the page means the call still records, but with nothing to attribute it to. ## Step 2: Create an API key in CallRail 1. Sign in to CallRail and click the **Settings** gear in the top right. 2. Open **API Keys** in the left sidebar. 3. Click **Create API Key**, name it `SourceLoop`, and copy the token. CallRail shows the token only once. If you navigate away before copying it, just create a fresh key. > **Create the key from an admin user** > A key generated by a user with no account permissions can't see any companies, and the connection will fail with "API key has no CallRail accounts visible to it." If you hit that, have a CallRail admin create the key instead. ## Step 3: Connect CallRail in SourceLoop 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Phone** in the left sidebar. 3. Find the **CallRail** card and click **Connect**. 4. Paste your API token into the **API key** field. 5. Leave **Account ID** blank unless your key can see more than one CallRail account. If it can, paste the specific account ID you want (it's in your CallRail dashboard URL, after `account_id=`). 6. Click **Connect**. That's the whole setup. SourceLoop validates the key, picks up your account, and registers call delivery for every active company on it. Within a few seconds the card flips to **Connected** with a green check. You don't need to open CallRail's integrations page or paste any URL yourself, SourceLoop wires that up for you. > **Optional: forward the visitor ID for a perfect match** > Out of the box, SourceLoop matches each call to a web session with high accuracy using the analytics identifier CallRail already captures. For a deterministic one-to-one match, tell CallRail to also carry SourceLoop's visitor ID. Where CallRail's number-swap script runs on your site, change `CallTrk.swap();` to: > > ```js > CallTrk.swap({ > custom: { sl_aid: 'sl_aid' }, > }); > ``` > > Now every call from a visitor with a SourceLoop session is matched exactly, no guesswork. This is optional, most accounts are fine without it. ## Step 4: Verify it's working Open your site in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=callrail-check` to the URL, then call the tracking number shown on the page and let the call connect for a few seconds. Within moments of hanging up, the call appears at the top of your **Contacts Hub** in SourceLoop with the three test UTM values, the landing page, the call duration, and the full session timeline that led up to it. > **Call shows up as Direct after calling from your own site?** > Usually the number didn't have a moment to swap before you dialed, for example you opened the page on mobile and tapped the number instantly. Give the page a second to load before calling, or set up the optional visitor-ID forwarding in Step 3 for a match that doesn't depend on timing. ## Where to see CallRail calls in SourceLoop ### Contacts Hub Every captured call becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open one to see the caller's complete journey before they picked up the phone: every page, every ad, every session. The context your reps wish they'd had on the call, and exactly what you need for the follow-up. ![SourceLoop Contacts Hub showing a CallRail phone call lead with the caller's full pre-call journey timeline](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the channel-level picture, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups calls by source, medium, and campaign right alongside your form and chat conversions, so you can finally see which channels drive the phone to ring, not just which fill out forms. ![SourceLoop attribution dashboard with CallRail phone calls grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) that ends in a phone call. Slice by source, landing page, or device to find the paths that most reliably turn a visit into a call. ![SourceLoop funnel report ending in a CallRail phone call conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition, forward your qualified calls back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms learn from real phone calls instead of raw clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. Using a different call tracking provider? SourceLoop also supports [CallTrackingMetrics](/help/track-lead-source-in-calltrackingmetrics/) and [Invoca](/help/track-lead-source-in-invoca/). ## Frequently Asked Questions ### Do I need a paid CallRail plan? You need a plan that lets you generate an API key, which is every paid CallRail plan. API access is included at no extra cost, and SourceLoop does not charge for the connection itself. Each captured call counts as one lead toward your SourceLoop quota, the same as a form or chat conversion. ### Do I have to set up webhooks inside CallRail myself? No. That is the part SourceLoop handles for you. The moment you paste your API key, SourceLoop registers the call delivery for every active company on the account. You never have to open CallRail's integrations page or copy a URL by hand. ### What if my CallRail account has several companies? SourceLoop subscribes to all of them automatically when you connect. Calls from any company on the account flow into the same SourceLoop workspace, each tagged with the number that was dialed. ### A customer dialed a tracking number off a billboard, not my website. Does that still get captured? Yes, the call is still recorded as a lead. Because there was no web session behind it, there is no journey to attach, so it shows up as Direct in your reports. Calls that started from a tracked page on your site carry full attribution. ### How quickly do calls appear in SourceLoop? Within seconds of the call ending. There is no batch import or polling delay, so a call that wraps up now is in your Contacts Hub almost immediately. ### What happens when I tag or qualify a call later inside CallRail? That update flows through to the same lead in SourceLoop. Tagging a call or marking it qualified updates the existing record rather than creating a duplicate, so your reports stay clean. ### Will connecting SourceLoop change anything in my CallRail setup? No. Your number pools, routing, recordings, and existing integrations keep working exactly as they did. SourceLoop only reads completed-call data, so nothing about how CallRail handles live calls changes. ### How do I disconnect CallRail? Open Setup then Phone in SourceLoop and click Disconnect on the CallRail card. Call delivery to SourceLoop stops immediately and previously captured leads stay in your Contacts Hub. The webhook entry remains on the CallRail side until you remove it under Settings then Integrations, where you can leave it idle or delete it for tidiness. --- # How to track lead source in CallTrackingMetrics Attribute every CallTrackingMetrics call to the channel behind it. Connect your API keys, point CTM's webhook at SourceLoop, and calls arrive. Source: https://sourceloop.ai/help/track-lead-source-in-calltrackingmetrics/ Updated: 2026-05-31 --- CallTrackingMetrics is built for teams that route, score, and report on a lot of calls. The one thing it usually can't tell you on its own is which marketing channel earned each call before your phone system ever picked it up. SourceLoop adds that layer. Once connected, every call to a CTM tracking number becomes a lead carrying its first-touch and last-touch source, the UTMs, the landing page, and the caller's full path across your site. CTM differs from CallRail in one way: it has no API for registering call delivery, so you point its webhook at SourceLoop by hand after connecting. SourceLoop generates the exact address and payload for you, so it stays a copy-and-paste job. Five steps, roughly ten minutes. ## What SourceLoop captures from CallTrackingMetrics For every call to a CTM tracking number, SourceLoop records a lead with **Type: Call** that carries: - **First-touch and last-touch source** of the calling visitor - **UTM parameters** from the landing URL (source, medium, campaign, content, term) - **The full pre-call journey**: pages browsed and ads clicked before the call, when the caller visited your site first - **Caller details**: phone number, name, and city or state when CTM provides them - **Call facts**: duration and call status - **Recording and transcription links**, when CTM has them - **Tag and status changes** you make to the call afterward in CTM ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - The **SourceLoop tracker installed** on the pages where your CTM numbers appear - A **CallTrackingMetrics account** with a tracking number live on your site - Access to **CTM's API Integration settings** and permission to **add a webhook** in CTM - **Admin** or **Owner** role in SourceLoop ## Step 1: Install the SourceLoop tracking script Each call's attribution comes from the browsing session that preceded it, so the tracker has to be live wherever your CTM numbers show. Already installed it for form, meeting, or chat tracking? You're set, move to Step 2. Otherwise, add the SourceLoop snippet across your site. See [Install the SourceLoop tracking pixel](/help/install-the-tracking-pixel/). ## Step 2: Get your API keys from CTM 1. In CallTrackingMetrics, open **Settings -> Account -> API Integration**. 2. Copy the **Access Key**. 3. Click **Show Secret Key** and copy the **Secret Key**. 4. If your account is one of several your login can reach, note your **Account ID** (the number after `/a/` in the CTM dashboard URL). ## Step 3: Connect CTM in SourceLoop 1. Sign in to [SourceLoop](https://app.sourceloop.ai/) and open **Setup -> Phone**. 2. Click **Connect** on the **CallTrackingMetrics** card. 3. Paste the **Access Key** and **Secret Key**, and the **Account ID** if you noted one. 4. Click **Connect**. SourceLoop validates the keys against your CTM account. Once they check out, the card expands a **Setup required in CallTrackingMetrics** panel, your instructions for the next step. ## Step 4: Point CTM's webhook at SourceLoop The **Setup required** panel gives you two things generated for your connection: a **webhook address** and a **recommended payload** that tells CTM which call fields to send. Follow the numbered steps in that panel to add a webhook inside CTM, paste in the address, drop in the payload, and save. Because the values are specific to your workspace, copy them straight from the panel rather than typing them out. > **Use the payload SourceLoop gives you** > The recommended payload maps CTM's call fields to the ones SourceLoop expects. If you save the webhook with an empty or hand-built body, calls may arrive missing their caller details or attribution. Paste the panel's template as-is, then save. > **Optional: forward the visitor ID for a perfect match** > By default SourceLoop matches calls to web sessions with high accuracy using the analytics identifier CTM already captures. For a deterministic one-to-one match, configure CTM's custom-cookie option to also carry SourceLoop's visitor ID. The recommended payload already leaves a slot for it, so once CTM is sending that value, the match becomes exact. CTM's support team can confirm the custom-cookie syntax for your plan. ## Step 5: Verify it's working Open your site in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=ctm-check` to the URL, then call the CTM tracking number shown on the page and stay connected for a few seconds. Shortly after the call ends, it appears at the top of your **Contacts Hub** in SourceLoop with the three test UTM values, the landing page, the call duration, and the full session that led up to it. If it doesn't, recheck that the webhook in CTM is saved with the recommended payload and is firing on completed calls. ## Where to see CallTrackingMetrics calls in SourceLoop ### Contacts Hub Every captured call becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open one to replay the caller's journey before they dialed, every page and every ad, so the follow-up starts with context instead of a cold callback. ![SourceLoop Contacts Hub showing a CallTrackingMetrics phone call lead with the caller's full pre-call journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard At [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic), calls are grouped by source, medium, and campaign next to your other conversions, so phone demand sits in the same channel report as everything else. ![SourceLoop attribution dashboard with CallTrackingMetrics calls grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) that ends in a phone call, then slice by source or landing page to see which paths most reliably produce a call. ![SourceLoop funnel report ending in a CallTrackingMetrics phone call conversion step](/help/screenshots/sourceloop-funnel.png) To make paid channels smarter, forward qualified calls back to **Google Ads, Meta, and LinkedIn as offline conversions**. [Connect your Google Ads account](/help/connect-google-ads/) walks through it. Prefer a one-click connect with no manual webhook step? [CallRail](/help/track-lead-source-in-callrail/) registers call delivery for you. For enterprise conversation intelligence, see [Invoca](/help/track-lead-source-in-invoca/). ## Frequently Asked Questions ### Why does CallTrackingMetrics need a manual webhook step when CallRail doesn't? CallRail exposes an API that lets SourceLoop register call delivery for you. CallTrackingMetrics doesn't, so after you connect your keys you paste SourceLoop's webhook address and a ready-made payload into CTM yourself. SourceLoop hands you the exact values and the steps on screen, so it's copy, paste, save. A few extra minutes, once. ### Where do I find my Access Key and Secret Key in CTM? In CallTrackingMetrics, go to Settings, then Account, then API Integration. Your Access Key is shown directly, and the Secret Key appears after you click Show Secret Key. Paste both into the SourceLoop connection dialog. ### Do I need my CTM Account ID? Only if your keys can reach more than one CTM account. If you have a single account, leave the field blank. When you do need it, the Account ID is the number in your CTM dashboard URL after /a/. ### What does the recommended payload template do? It tells CTM which call fields to send SourceLoop so attribution lines up correctly, including the caller details, the call status, and the marketing parameters. SourceLoop generates it for your connection, so you paste it as-is rather than building it by hand. ### A call came from a number printed offline, with no website visit. Is it still captured? Yes, it's recorded as a lead. With no web session behind it there's no journey to attach, so it appears as Direct. Calls that began on a tracked page carry full source and campaign detail. ### What happens when I change a call's tags or status in CTM later? That update reaches the same SourceLoop lead through the webhook, updating the existing record rather than creating a second one, as long as the recommended payload is in place on the matching trigger. ### How fresh is the data? Calls reach SourceLoop within seconds of CTM firing the webhook, which happens as the call wraps up. There's no batch import. ### How do I disconnect? Click Disconnect on the CallTrackingMetrics card under Setup then Phone in SourceLoop, then remove the webhook you added inside CTM. After that no further calls are sent, and your already-captured leads stay in the Contacts Hub. --- # How to track lead source in Invoca Connect Invoca so every tracked call arrives with its source, UTMs, the caller's journey, and Invoca AI signals mapped to lead tags and status. Source: https://sourceloop.ai/help/track-lead-source-in-invoca/ Updated: 2026-05-31 --- Invoca is where enterprise teams turn phone conversations into signals: a call gets scored, classified, and labeled as a sale, a qualified lead, an appointment. What Invoca doesn't natively close the loop on is which marketing channel produced each of those calls in the first place. SourceLoop connects that end to end. Every call to an Invoca tracking number becomes a lead with its first-touch and last-touch source, the UTMs, and the caller's full pre-call journey. And because Invoca's AI signals come across too, those leads arrive already tagged with the outcome, with sale and qualified signals lifting the lead's status automatically. Five steps. You'll connect your token, then point Invoca's webhook at SourceLoop using values it generates for you. ## What SourceLoop captures from Invoca For every call to an Invoca tracking number, SourceLoop records a lead with **Type: Call** that carries: - **First-touch and last-touch source** of the calling visitor - **UTM parameters** from the landing URL (source, medium, campaign, content, term) - **The full pre-call journey**: pages and ads the caller engaged with before dialing, when they visited your site first - **Caller details**: phone number, plus city, state, or country when Invoca has them - **Call facts**: duration and call disposition - **Recording and transcript links**, when Invoca has them - **Invoca's AI signals as tags and status**: signals like Sale, Qualified, or Appointment Made become lead tags, and sale or qualified signals push the lead to Won or Qualified ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - The **SourceLoop tracker installed** on the pages where your Invoca numbers appear - An **Invoca account** with a tracking number live on your site - Permission to **create an API credential** and **add a webhook** in Invoca - Your Invoca **vanity subdomain** (the first part of your Invoca URL) - **Admin** or **Owner** role in SourceLoop ## Step 1: Install the SourceLoop tracking script A call's source comes from the browsing session ahead of it, so the tracker has to be live wherever your Invoca numbers appear. If it's already running for your other conversions, skip to Step 2. If not, add the SourceLoop snippet across your site. See [Install the SourceLoop tracking pixel](/help/install-the-tracking-pixel/). ## Step 2: Create an API credential in Invoca 1. In Invoca, open **Integrations -> Manage Integrations -> Invoca APIs**. 2. Click **+ New API Credential** in the top right and copy the token. 3. Note your **vanity subdomain**: the first part of your Invoca URL, so `acme.invoca.net` means you enter `acme`. ## Step 3: Connect Invoca in SourceLoop 1. Sign in to [SourceLoop](https://app.sourceloop.ai/) and open **Setup -> Phone**. 2. Click **Connect** on the **Invoca** card. 3. Paste your **API token** and your **vanity subdomain**. 4. Click **Connect**. SourceLoop validates the token against your Invoca tenant. Once it's confirmed, the card expands a **Setup required in Invoca** panel with everything you need for the next step. ## Step 4: Point Invoca's webhook at SourceLoop The **Setup required** panel gives you a **webhook address** and a **recommended payload**, both generated for your connection. Follow the numbered steps in the panel to open Invoca's **Custom Webhooks** settings, add a webhook, paste in the address and the payload, and save. Copy the values straight from the panel since they're unique to your workspace. > **Use the payload SourceLoop gives you** > The recommended payload maps Invoca's call fields, including its AI signals, to what SourceLoop expects. Saving the webhook without it means calls may arrive missing their attribution or their signal tags. Paste the panel's template as-is. > **Optional: forward the visitor ID for a perfect match** > Out of the box SourceLoop matches calls to web sessions using Invoca's standard attribution fields. For a deterministic one-to-one match, configure Invoca's Advertiser Web Integration on your site to also carry SourceLoop's visitor ID. The recommended payload already includes a slot for it. Whether you're a Network or Advertiser user changes the exact setup, so Invoca's onboarding team can wire it up. ## Step 5: Verify it's working Open your site in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=invoca-check` to the URL, then call the Invoca tracking number on the page and stay on for a few seconds. Soon after the call ends, it shows at the top of your **Contacts Hub** in SourceLoop with the three test UTM values, the landing page, the duration, and the full pre-call session. If Invoca has already classified the call, you'll see its signals on the lead as tags. If nothing appears, confirm the webhook in Invoca is saved with the recommended payload and is firing on completed calls. ## Where to see Invoca calls in SourceLoop ### Contacts Hub Each captured call becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts), complete with the caller's pre-call journey and any Invoca signals as tags, so the call's marketing origin and its outcome sit on the same record. ![SourceLoop Contacts Hub showing an Invoca phone call lead with the caller's full journey and AI signal tags](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard At [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic), calls roll up by source, medium, and campaign alongside your other conversions. Because Invoca's qualified and sale signals carry through, you can see which channels drive calls that actually convert, not just calls that connect. ![SourceLoop attribution dashboard with Invoca calls grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) that ends in a qualified phone call, then slice by source or landing page to find the paths that produce your best conversations. ![SourceLoop funnel report ending in an Invoca phone call conversion step](/help/screenshots/sourceloop-funnel.png) To train paid channels on real outcomes, forward Invoca's qualified and won calls back to **Google Ads, Meta, and LinkedIn as offline conversions**. [Connect your Google Ads account](/help/connect-google-ads/) covers the setup. Need a lighter-weight option? [CallRail](/help/track-lead-source-in-callrail/) connects in one click with no manual webhook step, and [CallTrackingMetrics](/help/track-lead-source-in-calltrackingmetrics/) is a strong mid-market choice. ## Frequently Asked Questions ### Who is the Invoca integration for? Teams already using Invoca for conversation intelligence at scale. If you mainly need call tracking with the least setup, CallRail is usually the smoother fit because it registers call delivery automatically. Invoca's edge here is that its AI signals come through to SourceLoop as lead tags and status. ### What is a vanity subdomain and where do I find it? It's the first part of your Invoca web address. If you sign in at acme.invoca.net, your vanity subdomain is acme. SourceLoop needs it alongside your API token to reach the right Invoca tenant. ### How do I create the API token? In Invoca, go to Integrations, then Manage Integrations, then Invoca APIs, and click New API Credential in the top right. Copy the token it generates and paste it into the SourceLoop connection dialog. ### How do Invoca's AI signals show up in SourceLoop? Each signal Invoca attaches to a call, such as Sale, Qualified, or Appointment Made, becomes a tag on the SourceLoop lead. Signals that indicate a sale or a qualified call also lift the lead's status to Won or Qualified, so your reports reflect call outcomes, not just that a call happened. ### Why is there a manual webhook step? SourceLoop validates your token to confirm the connection, then you point Invoca's webhook at SourceLoop yourself using the address and payload SourceLoop generates for you. It's a copy-and-paste step inside Invoca's webhook settings, shown on screen after you connect. ### A call came in with no preceding website visit. Is it still captured? Yes. It records as a lead, and with no web session to attach it falls back to Invoca's own attribution fields or shows as Direct. Calls that started on a tracked page carry the full journey. ### How fresh is the data? Calls reach SourceLoop within seconds of Invoca firing the webhook as the call completes. No batch import or delay. ### How do I disconnect? Click Disconnect on the Invoca card under Setup then Phone in SourceLoop, then remove the webhook inside Invoca. No further calls are sent afterward, and your captured leads remain in the Contacts Hub. --- # How to track lead source in Crisp Every Crisp conversation, paired with the marketing channel that brought the visitor to your site. No more cold replies to leads with no context. Source: https://sourceloop.ai/help/track-lead-source-in-crisp/ Updated: 2026-05-28 --- Crisp is the all-in-one customer messaging platform that startup teams reach for when they want chat, helpdesk, and CRM in one place. The widget is great at starting conversations. What it doesn't tell you is which campaign brought that visitor to your site in the first place. SourceLoop fills in that gap. Three steps, around five minutes, and every Crisp conversation afterwards arrives with the marketing context behind it. ## What SourceLoop captures from Crisp Once Crisp is on a page tracked by SourceLoop, each conversation flows into SourceLoop tagged with: - **The visitor's acquisition channel** (organic, paid, referral, social, direct) - **UTM parameters** parsed from the landing URL - **Pages browsed** in order before the chat started - **Time on site** ahead of the conversation - **Number of distinct sessions** before this lead chatted - **Email + name** captured during the conversation - **First-touch landing page** of the visitor's history - **Source of the converting session** that produced the chat - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to your site's `` markup - A **Crisp account** with the chat widget already embedded on your site ## Step 1: Drop SourceLoop's snippet into your site Open SourceLoop, go to **Setup -> Tracking code** in the sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into your site's ``. The usual paths: - WordPress: header-injection plugin or `header.php` - Webflow: Project Settings -> Custom Code -> Head Code - Framer: Site Settings -> General -> Custom Code -> Start of head - Shopify: Online Store -> Themes -> Edit code -> `theme.liquid` - Tag manager: an All Pages tag SourceLoop and Crisp can sit side-by-side, the order they load doesn't matter, as long as both run on every page where the Crisp widget appears. ## Step 2: Confirm the Crisp widget is on tracked pages There's no per-account toggle to flip on Crisp's side. Once the SourceLoop snippet loads on a page where Crisp also loads, conversations from that page flow into SourceLoop automatically. Worth verifying: - The Crisp widget is **active on every page** where you want to capture leads (which, for most sites, is the entire site) - Your pre-chat survey or chatbot **collects an email** at some point in the conversation - Aggressive performance plugins or browser-only privacy modes aren't blocking either Crisp or SourceLoop > **Crisp's hosted chat URLs aren't attributable** > Conversations started through Crisp's hosted link (`go.crisp.chat/...`) skip your tracked site, so the visitor's marketing source isn't available. For attribution, send campaigns to a page on your own site that loads the Crisp widget. ## Step 3: Send a verification chat Open a page on your site that hosts the Crisp widget in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=crisp-check` appended to the URL. Start a chat, share an email address you can access. Within seconds of sharing the email, the conversation should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see Crisp conversations in SourceLoop ### Contacts Hub Every Crisp conversation that shares an email becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact to see the visitor's complete browsing path before they reached out, helpful context to read into your reply. ![SourceLoop Contacts Hub showing a Crisp conversation lead with the visitor's full pre-chat journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the channel-level read, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Crisp conversations by source, medium, and campaign. A quick read on which channels actually open dialogue vs. which only send pageviews. ![SourceLoop attribution dashboard with Crisp conversations grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "Crisp conversation". Slice by source, landing page, or device to find which routes actually drive engagement, not just visits. ![SourceLoop funnel report ending in a Crisp conversation conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition feeds the chat queue, forward Crisp conversations back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on actual qualified conversations rather than vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with Crisp's free plan? Yes. SourceLoop's tracking is browser-side and tier-agnostic, free Crisp, Pro, and Unlimited all work the same way once the widget loads on a tracked page. ### Crisp lets visitors message anonymously before sharing email. Are those tracked? SourceLoop attaches the marketing journey to the contact once an email is shared (anywhere in the conversation, including via the pre-chat survey or a follow-up). Truly anonymous conversations stay anonymous, the same as they do in Crisp itself. ### I use Crisp's Chatbot to qualify leads before handing off. Does the chatbot path still get attributed? Yes. The chatbot conversation is what eventually captures an email, and that email is what attribution ties to. Whether a human or a bot collected the email doesn't matter. ### I send my Crisp link (`go.crisp.chat/...`) in cold emails. Will those replies be attributed? No. Conversations started through Crisp's hosted link skip your site entirely, so SourceLoop never sees the visitor. For attribution, send recipients to a page on your site where the Crisp widget is loaded. ### Does this interfere with Crisp's existing integrations (HubSpot, Salesforce, Slack, etc.)? No. Crisp continues to push contacts and conversations to all your connected destinations exactly as configured. SourceLoop captures attribution on its own side, no overlap. --- # How to track lead source in GoHighLevel Chat Bring real source attribution into your GoHighLevel chat workflow so each conversation surfaces which campaign or channel actually drove the lead. Source: https://sourceloop.ai/help/track-lead-source-in-gohighlevel-chat/ Updated: 2026-05-28 --- GoHighLevel Chat is the messaging surface tied into the rest of the GHL stack, pipelines, automations, SMS, email, calendar. The whole ecosystem leans on conversations as the entry point. The marketing piece, knowing which campaign sourced each chat, isn't in GoHighLevel's reporting. SourceLoop fills it in. Three steps, about ten minutes, and every GHL chat afterwards carries the attribution context behind it. ## What SourceLoop captures from GoHighLevel Chat Each chat conversation routed through GoHighLevel lands in SourceLoop with: - **The visitor's marketing source** (organic, paid, referral, social, direct) - **UTM parameters** captured from the landing URL - **Pages visited** in chronological order before the chat - **Time on site** ahead of the conversation - **Number of distinct sessions** before they reached out - **Email + name** captured during the conversation - **First-touch landing page** of the visitor's journey - **Source of the converting session** that produced the chat - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the site (or GHL funnel) hosting your chat widget - A **GoHighLevel sub-account** with the chat widget installed on the client site ## Step 1: Install the SourceLoop snippet on the client site Inside SourceLoop, head to **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add the snippet to the `` of the client's site: - **GoHighLevel funnel / website**: open the funnel or site builder, click the page's gear icon, and paste the snippet into the **Head Tracking Code** field - **WordPress**: a header-injection plugin or `header.php` - **Other CMS / static site**: the global layout template Wherever the GHL chat widget appears, the SourceLoop snippet needs to load alongside it. ## Step 2: Confirm the GoHighLevel chat widget is live on tracked pages GoHighLevel's chat widget doesn't need any per-account configuration to play nicely with SourceLoop. Once the snippet runs on the same page as the widget, chat conversations from that page get attributed. Quick verification: - The GHL chat widget is **active** in your sub-account settings (Sites -> Chat Widget) - The widget code is **embedded** on the client site, alongside SourceLoop - Your chat flow **captures email** at some point (pre-chat survey, mid-conversation, or post-conversation summary) > **Chats from GoHighLevel-hosted booking URLs and outbound texts aren't attributable** > Conversations that start through GoHighLevel's direct widget URLs, outbound SMS, or hosted forms skip your tracked site, so SourceLoop has no marketing journey to attach. Route campaigns to a page on the client's site that loads the GHL chat widget. ## Step 3: Run a verification chat Visit the client site in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=ghl-chat-check` glued to the URL. Open the GHL chat widget and start a conversation, sharing an email you can access. Within seconds of the email being captured, the conversation should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see GoHighLevel chat conversations in SourceLoop ### Contacts Hub Each GHL chat that captures an email becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Drill into a contact for the visitor's complete pre-chat browsing history, useful context before the rep (or automation) replies. ![SourceLoop Contacts Hub showing a GoHighLevel chat lead with the visitor's full pre-chat journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the cross-channel view, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups GHL chat conversations by source, medium, and campaign. Particularly useful for agencies reporting to clients on which channels are pulling weight. ![SourceLoop attribution dashboard with GoHighLevel chat conversations grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "GoHighLevel chat". Slice by source, landing page, or device to find the highest-converting routes from first visit to chat start. ![SourceLoop funnel report ending in a GoHighLevel chat conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition feeding the chat queue, mirror GHL conversations back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on actual qualified conversations instead of vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work on every GoHighLevel plan? Yes. The GoHighLevel chat widget loads the same way across Starter, Unlimited, and SaaS Mode tiers, and SourceLoop attaches attribution to conversations regardless of which GoHighLevel plan you (or your sub-account clients) are on. ### I'm an agency running multiple GoHighLevel sub-accounts. Can I track each client separately in SourceLoop? Yes. Create a separate website/workspace in SourceLoop for each sub-account, install that workspace's snippet on the corresponding client site, and the conversations stay isolated per client. ### GoHighLevel chat captures phone, email, or both. Will SourceLoop attach attribution either way? SourceLoop uses email as the primary lead identifier. If your chat captures email (even via the post-conversation summary), attribution attaches. Phone-only conversations stay as phone leads inside GoHighLevel. ### Will my GoHighLevel automations and pipeline stages still update when SourceLoop is added? Yes. SourceLoop runs in parallel on its own side, capturing attribution. GoHighLevel's automations, pipeline updates, SMS follow-ups, and email sequences continue to fire exactly as configured. ### I send SMS replies through GoHighLevel. Is the original chat still attributed? Yes. The chat conversation is when SourceLoop attaches the source. Subsequent channels (SMS replies, follow-up emails, calls) all hang off the same contact record in GoHighLevel, and SourceLoop's attribution stays attached. --- # How to track lead source in LiveChat Give LiveChat reps the marketing context they need before they reply. Every conversation arrives with the source, campaign, and pre-chat journey. Source: https://sourceloop.ai/help/track-lead-source-in-livechat/ Updated: 2026-05-28 --- LiveChat has been in the customer-conversation game longer than most, polished workflows, deep CRM integrations, mature analytics inside its own platform. The one report it can't show you is which marketing channel actually delivered each visitor to the chat. SourceLoop layers that data on without rewriting any of your LiveChat setup. Three steps, around five minutes, attribution flowing on every conversation afterwards. ## What SourceLoop captures from LiveChat Every LiveChat conversation that captures an email lands in SourceLoop tagged with: - **Acquisition channel** of the visitor (organic, paid, referral, social, direct) - **UTM parameters** from the landing URL - **Pages browsed** in chronological order before the chat - **Time on site** ahead of the conversation - **Number of distinct sessions** before they engaged - **Email + name** captured during the chat - **First-touch landing page** of the visitor's history - **Source of the converting session** that produced the conversation - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to your site's `` markup - A **LiveChat account** with the widget already embedded on your site ## Step 1: Add SourceLoop's snippet to your site Inside SourceLoop, head to **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to your site's ``. Common paths: - WordPress: header-injection plugin or `header.php` - Webflow: Project Settings -> Custom Code -> Head Code - Framer: Site Settings -> General -> Custom Code -> Start of head - Shopify: Online Store -> Themes -> Edit code -> `theme.liquid` - Tag manager: an All Pages tag LiveChat and SourceLoop coexist happily, both can load on the same page, the order doesn't matter. They just both need to be on every page where you want to capture leads from chat. ## Step 2: Confirm the LiveChat widget is on tracked pages No configuration changes needed inside LiveChat. Once SourceLoop loads on a page where the LiveChat widget also loads, conversations from that page are attributed automatically. Worth checking: - The LiveChat widget is **enabled** on every page where you want to capture leads - The widget's **pre-chat form** or chatbot flow captures an email at some point - Your performance plugins or ad blockers aren't blocking either LiveChat or SourceLoop > **Chats from direct LiveChat URLs aren't attributable** > LiveChat hosts conversations at its own direct URL (`direct.lc.chat/...`) when shared externally. Visitors hitting that URL never load your tracked site, so SourceLoop can't pin a marketing source to the conversation. Route campaigns to your own pages where the widget loads. ## Step 3: Send a verification chat Open your site in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=livechat-check` appended to the URL. Click the LiveChat widget, start a conversation, and share an email you can access. Within seconds of the email being captured, the conversation should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see LiveChat conversations in SourceLoop ### Contacts Hub Every LiveChat conversation that captures an email becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact for the visitor's complete pre-chat browsing path, useful context before your rep replies. ![SourceLoop Contacts Hub showing a LiveChat conversation lead with the visitor's full pre-chat journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the channel rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups LiveChat conversations by source, medium, and campaign. A quick read on which channels actually open dialogue with real prospects vs. which only generate pageviews. ![SourceLoop attribution dashboard with LiveChat conversations grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "LiveChat conversation". Slice by source, landing page, or device to find which routes drive real engagement. ![SourceLoop funnel report ending in a LiveChat conversation conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition fills the chat queue, forward LiveChat conversations back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms optimise toward actual qualified conversations. [Connect your Google Ads account](/help/connect-google-ads/) walks through the setup. ## Frequently Asked Questions ### Does this work on LiveChat's Starter plan? Yes. SourceLoop's tracking is browser-side and tier-agnostic, every LiveChat tier from Starter to Enterprise behaves the same once the widget loads on a tracked page. ### I use both LiveChat and ChatBot.com (same vendor, different product). Does the bot get attributed too? Yes, the chat that captures email is what attribution attaches to. Whether the conversation is human-handled in LiveChat or bot-handled via ChatBot doesn't matter, the email collected during the flow ties the journey to the contact. ### My LiveChat is configured with a pre-chat survey. Will email collected there work? Yes. The pre-chat survey email is captured the same way as one shared mid-conversation. SourceLoop attaches attribution as soon as the email is identified. ### Does this conflict with LiveChat's existing Salesforce, HubSpot, or Pipedrive integrations? No. LiveChat continues to sync conversations and contacts to every connected CRM exactly as configured. SourceLoop captures attribution on its own side, so the same contact appears in your CRM with full conversation history and in SourceLoop with marketing source data. ### We support LiveChat across mobile apps and web. Does mobile-app chat get tracked? Web sessions are what SourceLoop tracks (marketing channels live on the web). Mobile-app chats inside your native app go through LiveChat's mobile SDK and don't carry a marketing source in the same way. SourceLoop attribution covers web visitors specifically. --- # How to track lead source in Tawk.to Add marketing attribution to every Tawk.to conversation so your free chat widget finally tells you which channel sent each visitor your way. Source: https://sourceloop.ai/help/track-lead-source-in-tawk-to/ Updated: 2026-05-28 --- Tawk.to has built a huge following by giving away a perfectly capable live chat widget for free, no tiers, no upsell wall. The trade-off is that its built-in analytics stay basic, you can see chats but not where the chatter came from. SourceLoop adds that missing context. Three steps, around five minutes, attribution flowing on every conversation afterwards. ## What SourceLoop captures from Tawk.to Each Tawk.to conversation that captures an email arrives in SourceLoop with: - **The visitor's acquisition source** (organic, paid, referral, social, direct) - **UTM parameter values** from the landing URL - **Pages browsed** in chronological order before the chat - **Total time on site** before they opened the widget - **Number of distinct sessions** before they finally engaged - **Email + name** captured during the conversation - **First-touch landing page** of the visitor's journey - **Source of the converting session** that produced the chat - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to your site's `` markup - A **Tawk.to account** with the widget code already embedded on your site ## Step 1: Install the SourceLoop snippet Open SourceLoop, go to **Setup -> Tracking code** in the sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Add it to the `` of your site. Easy ways: - WordPress: header-injection plugin or `header.php` - Webflow: Project Settings -> Custom Code -> Head Code - Framer: Site Settings -> General -> Custom Code -> Start of head - Shopify: Online Store -> Themes -> Edit code -> `theme.liquid` - Static / custom: the global layout template The SourceLoop snippet sits alongside the Tawk.to snippet, neither one cares about loading order, both just need to be on every page where the widget shows. ## Step 2: Confirm Tawk.to is loading on tracked pages Tawk.to doesn't need any per-widget configuration to integrate with SourceLoop. Once the SourceLoop snippet runs on the same page as the Tawk.to widget, conversations get attributed. Quick sanity check: - The Tawk.to widget **shows** on every page where you want chat leads (Settings -> Property -> Widget) - Your widget setup **captures email** somewhere (pre-chat form, post-chat survey, or directly mid-conversation) - No security plugins or aggressive ad blockers are stopping either Tawk.to or SourceLoop from loading > **Hosted Tawk.to direct-chat URLs aren't attributable** > Conversations started through Tawk.to's hosted direct-chat link bypass your site entirely. No tracking script loads, so SourceLoop can't pin a marketing source to the conversation. Always route campaigns to a page on your own site where the widget shows. ## Step 3: Send a verification chat Open your site in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=tawk-check` glued to the URL. Click the Tawk.to widget, start a conversation, and share an email you can access. Within seconds of the email being captured, the conversation should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see Tawk.to conversations in SourceLoop ### Contacts Hub Every Tawk.to chat that captures an email becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click into a contact to see the visitor's complete pre-chat browsing path. ![SourceLoop Contacts Hub showing a Tawk.to conversation lead with the visitor's full pre-chat journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the campaign-level rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Tawk.to conversations by source, medium, and campaign. Quick read on which channels open the most dialogue. ![SourceLoop attribution dashboard with Tawk.to conversations grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports In [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels), build a funnel ending in "Tawk.to conversation". Slice by source, landing page, or device to find the highest-converting paths from first visit to chat. ![SourceLoop funnel report ending in a Tawk.to conversation conversion step](/help/screenshots/sourceloop-funnel.png) If paid acquisition feeds the chat queue, mirror Tawk.to conversations back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real qualified conversations rather than vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Tawk.to is completely free. Does that affect SourceLoop's capability? No. SourceLoop's tracking is browser-side and totally unrelated to Tawk.to's pricing model. The free widget plays exactly the same way as a paid chat tool. ### I use Tawk.to with their paid "Hire an Agent" service. Are those conversations attributed too? Yes. The hired agent uses the same chat widget on your site, so any conversation that captures an email gets attribution attached, regardless of whether the agent is in-house or sourced through Tawk.to. ### Can I track Tawk.to's AI Assist (Apollo) responses? Yes. AI replies happen inside the Tawk.to widget, which loads on your tracked page. The attribution attaches to the contact once an email is shared, whether the visitor is talking to AI or a human. ### Does this affect Tawk.to's Departments and routing rules? No. Tawk.to's department routing, agent assignment, business hours, and triggers all continue to work normally. SourceLoop runs independently on its own side. ### I run Tawk.to on multiple sites under one account. Can each site get separate attribution? Yes. Create a website/workspace in SourceLoop for each site you operate, install that workspace's snippet on the corresponding site, and the attribution data stays separated per site. --- # How to track lead source in Tidio Stop replying to Tidio chats with no context. Every conversation arrives tagged with the channel, campaign, and journey that delivered the visitor. Source: https://sourceloop.ai/help/track-lead-source-in-tidio/ Updated: 2026-05-28 --- Tidio sits in the sweet spot between live chat and chatbot, popular with ecommerce stores running Shopify and WooCommerce, with AI-driven bot (Lyro) and email marketing baked in. The reporting it doesn't surface is the one most marketers want, which campaign brought each chatter to the site. SourceLoop covers that without changing anything inside Tidio. Three steps, around five minutes, attribution applied to every conversation afterwards. ## What SourceLoop captures from Tidio For each Tidio conversation that captures an email, SourceLoop attaches: - **The visitor's marketing source** (organic, paid, social, referral, direct) - **UTM parameter set** parsed from the landing URL - **Pages visited** in order before the chat - **Time on site** before the conversation - **Number of distinct sessions** preceding engagement - **Email + name** captured during the chat - **First-touch landing page** of the visitor's history - **Source of the converting session** that produced the chat - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to your storefront's `` markup - A **Tidio account** with the chat widget already installed on your site ## Step 1: Install the SourceLoop snippet on your store Inside SourceLoop, navigate to **Setup -> Tracking code** in the sidebar and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into the `` of your store. Common ecommerce paths: - **Shopify**: Online Store -> Themes -> Edit code -> `theme.liquid`, before `` - **WooCommerce**: a header-injection plugin like Insert Headers and Footers - **BigCommerce**: Storefront -> Script Manager, head placement on All Pages - **Wix**: Settings -> Custom Code -> head placement on All Pages - **Custom build**: your global layout template Tidio's widget snippet stays exactly where it is. The two coexist with no special configuration. ## Step 2: Confirm Tidio is loading on tracked pages No configuration changes needed inside Tidio. Once SourceLoop runs on the same page as the Tidio widget, conversations that capture an email get attributed automatically. Worth verifying: - The Tidio widget is **active** in your account settings (Channels -> Live Chat) - The chat flow **captures email** at some point, via pre-chat survey, Lyro's conversational flow, or post-chat form - Your store's performance plugins aren't blocking either Tidio or SourceLoop > **Tidio's direct-chat URLs aren't attributable** > If you share Tidio's hosted direct-chat link in emails or social, visitors hit Tidio's domain instead of your store. Those conversations don't carry a marketing source in SourceLoop. Route campaign traffic to your store's pages where the widget loads. ## Step 3: Send a verification chat Visit your store in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=tidio-check` glued onto the URL. Open the Tidio widget, start a conversation, and share an email you can access. Within seconds of the email being captured, the conversation should appear at the top of the **Contacts Hub** in SourceLoop with the three test UTM values stamped on the record. ## Where to see Tidio conversations in SourceLoop ### Contacts Hub Every Tidio conversation that shares an email becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Click into a contact for the visitor's full pre-chat browsing path, useful context to read into your reply or your bot's next response. ![SourceLoop Contacts Hub showing a Tidio conversation lead with the visitor's full pre-chat journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Tidio conversations by source, medium, and campaign so you can see which channels open the most dialogue with real prospects, particularly useful for paid social channels feeding your store traffic. ![SourceLoop attribution dashboard with Tidio conversations grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Tidio conversation". Slice by source, landing page, or device to find which routes drive real engagement. ![SourceLoop funnel report ending in a Tidio conversation conversion step](/help/screenshots/sourceloop-funnel.png) If your store runs paid acquisition, mirror Tidio conversations back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real qualified conversations instead of vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with the Tidio free plan? Yes. Tracking happens in the browser independent of Tidio's pricing tier. Free, Starter, Communicator, the experience is identical. ### Tidio's AI bot Lyro can handle entire conversations. Do those bot chats get attributed? Yes. The conversation is what carries the email, regardless of whether Lyro or a human agent handles it. SourceLoop attaches attribution as soon as the email is identified, AI-handled or not. ### We run Tidio on a Shopify store. Does the order/cart data sync stay intact? Yes. Tidio's Shopify integration (cart contents, order history shown alongside conversations) continues to work exactly as before. SourceLoop just adds the marketing-source layer to the contact in its own dashboard. ### I use Tidio Email Marketing to send broadcasts. Are conversations from email recipients tracked? Yes, if the email's UTMs are intact. When recipients click through to your site, SourceLoop picks up the UTMs from the landing URL and attaches them to any conversation that follows. ### Will Tidio's integrations (Zapier, HubSpot, Klaviyo) keep firing as configured? Yes. Tidio continues to sync conversations and contacts to all your connected destinations. SourceLoop saves an attribution-rich copy of the lead on its own side, with no overlap on Tidio's outbound flow. --- # How to track lead source in Intercom Track lead source in Intercom with one-click OAuth. Every chat that captures an email gets attribution, UTMs, and the visitor's prior journey. Source: https://sourceloop.ai/help/track-lead-source-in-intercom/ Updated: 2026-05-29 --- Intercom is one of the few chat tools where every meaningful conversation passes through the Inbox. The piece that's usually missing is **which marketing channel earned each conversation**, the source data your reps need before they hit reply. SourceLoop closes that gap with a one-click OAuth connection. Every chat that captures an email lands in the SourceLoop Contacts Hub with first-touch and last-touch source, UTMs, landing page, and the full pre-chat journey. Two steps, about three minutes. Works on every Intercom plan. ## What SourceLoop captures from Intercom For every Intercom conversation that surfaces an email or phone number, SourceLoop attaches: - **First-touch source** (e.g., `google / cpc`, `linkedin / organic`) - **Last-touch source** of the converting session - **UTM parameters** from the landing URL (source, medium, campaign, content, term) - **First-touch landing page** plus the referrer chain - **Pages browsed** before the chat started - **Time on site** and **return-visit count** - **Contact details** from the Intercom contact record (email, name, phone) - **Device, country, browser** of the converting session ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - A **SourceLoop tracker installed** on every page where the Intercom Messenger appears - An **Intercom workspace** with the Messenger embedded on your site - **Admin** access on the Intercom workspace (required to approve the OAuth consent screen) - **Admin** or **Owner** role in SourceLoop ## Step 1: Connect Intercom via OAuth 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Chat** in the left sidebar. 3. Find the **Intercom** card under **OAuth chat integrations** and click **Connect**. ![SourceLoop Setup Chat page showing the OAuth chat integrations section with HubSpot and Intercom cards, both with Connect buttons](/help/screenshots/connect-hubspot-intercom.png) 4. You'll be redirected to Intercom's OAuth consent screen. Sign in with an admin user, pick the workspace, and click **Authorize access**. ![Intercom OAuth consent screen titled SourceLoop wants to access your workspace, listing People, Companies, Conversations, Teams and teammates, Tags, and Data attributes scopes, with an Authorize access button](/help/screenshots/intercom-auth.png) 5. Intercom redirects you back to SourceLoop. The card flips to **Connected** with the workspace name displayed. What SourceLoop does in the background, automatically: - Stores the OAuth access token, encrypted at rest - Creates a custom attribute on your Intercom contact model called **sourceloop_id**, marked as Messenger-writable so the tracker can populate it without code changes on your site - Subscribes to the Intercom webhook topics that fire when a visitor identifies (adds email) or starts / replies to a conversation No further configuration needed. The tracker on your site automatically pushes the visitor's marketing attribution onto their Intercom contact every time the Messenger loads. > **Why does SourceLoop need to create a custom attribute?** > Intercom silently drops attribute updates from the Messenger unless the attribute is defined on the contact model AND marked Messenger-writable. The sourceloop_id attribute is what lets the tracker tag each visitor's contact with their SourceLoop identity so the webhook can match the chat to their pre-chat journey. The field stays out of the way and you don't need to surface it in any view. ## Step 2: Verify it's working Open your site in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=intercom-check` to the URL, click the Intercom Messenger, and start a real conversation, sharing an email you can access. Within seconds of the email landing, the lead appears at the top of the **Contacts Hub** in SourceLoop with the three test UTM values, the converting landing page, and the full pre-chat session timeline. > **Not seeing the conversation in SourceLoop?** > Open the page with `?sl_debug=1` appended to surface SourceLoop's event log in the browser console. If pageviews are being captured but the conversation isn't appearing, confirm the connection card on **Setup -> Chat** is showing **Connected** (not **Pending** or **Error**), and check that the Messenger is loading on the same domain the tracker is installed on. ## Where to see Intercom conversations in SourceLoop ### Contacts Hub Every Intercom conversation that captures an email or phone becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact for the visitor's complete pre-chat journey, every page they viewed, every ad they clicked, every session they had, useful context if you ever need to reopen the conversation. ![SourceLoop Contacts Hub showing an Intercom conversation lead with the visitor's full pre-chat journey timeline](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the channel-level rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Intercom conversations by source, medium, and campaign so you can compare which channels actually open meaningful dialogue. ![SourceLoop attribution dashboard with Intercom conversations grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Intercom conversation". Slice by source, landing page, or device to find the highest-converting paths from first visit to chat opened. ![SourceLoop funnel report ending in an Intercom conversation conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition feeding the chat queue, forward Intercom conversations back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real qualified conversations rather than vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. Need to revoke access later? See [How to disconnect Intercom from SourceLoop](/help/disconnect-intercom-from-sourceloop/) for the full flow, including what's retained, what to clean up manually in Intercom, and how to request GDPR data removal. ## Frequently Asked Questions ### Do I need a paid Intercom plan? No. The OAuth connection works on every Intercom plan, including Essential, Advanced, Expert, and the Starter tier. Nothing in the setup requires a paid feature. ### What permissions does SourceLoop need on Intercom? Read access to contacts and conversations so SourceLoop can pull the conversation thread and the contact's email when a chat fires its webhook, plus the one-time data-attribute write that creates the sourceloop_id field on your contact model. Intercom's OAuth screen shows the full list before you approve. ### How does SourceLoop know which conversation belongs to which visitor? When a visitor opens the Messenger on a SourceLoop-tracked page, the tracker pushes an attribute called sourceloop_id onto their Intercom contact record. When the visitor submits an email and Intercom sends the webhook, SourceLoop reads sourceloop_id off the contact and stitches the conversation to the visitor's full pre-chat journey. No code changes required on your site beyond installing the tracker. ### Why does SourceLoop create a sourceloop_id custom attribute on my Intercom contact model? Because Intercom drops attribute updates from the Messenger SDK unless the attribute is defined on the contact model first and marked as Messenger-writable. SourceLoop creates the attribute once during the OAuth connect step and flips the Messenger-writable flag on. No further setup needed; the field stays out of the way and your team doesn't need to touch it. ### What about conversations started via a shared Intercom link, email reply, or off-site DM? SourceLoop still captures them via the Intercom webhook, but without a pre-chat browsing journey they show up tagged as Direct since there's no UTM trail. The contact details (email, name) still land in the SourceLoop Contacts Hub. ### How fresh is the data? Conversations appear in SourceLoop within seconds of the visitor submitting an email. The OAuth webhook is processed live as Intercom delivers it, no batch jobs. ### Will my existing Intercom workflows (Series, Custom Bots, Salesforce sync) still fire? Yes. SourceLoop's OAuth app subscribes to its own webhook events and only reads contact and conversation data. Every other Intercom workflow you have configured continues to fire unchanged. ### How do I disconnect Intercom? Open Setup -> Chat in SourceLoop, click Disconnect on the Intercom card. The OAuth token is revoked immediately, no further webhooks are processed, and previously captured leads stay in your Contacts Hub. You can reconnect any time with the same OAuth click. --- # How to disconnect Intercom from SourceLoop Disconnect SourceLoop from Intercom. What stops, what's retained on each side, what to clean up manually, and how to request full GDPR data removal. Source: https://sourceloop.ai/help/disconnect-intercom-from-sourceloop/ Updated: 2026-05-29 --- This article covers the complete Intercom disconnect flow, what stops immediately, what data we retain, what gets left behind in Intercom, and how to request full data removal under GDPR. It exists to give you (and your data-protection officer, if you have one) a clear answer to every reasonable question about what SourceLoop does with Intercom-sourced data after you decide to disconnect. ## Before you start A clear-headed expectation about what disconnect does and doesn't do: - **Disconnecting on SourceLoop's side** revokes the Intercom OAuth token and stops capturing conversations. - **Disconnecting on Intercom's side** removes SourceLoop from your Authorized Apps list (recommended for a clean audit trail, but optional). - **Neither action deletes data from Intercom itself**, SourceLoop has never had delete permissions on your contacts or conversations, and disconnect doesn't trigger any kind of data cleanup on Intercom's end. - **Existing SourceLoop data stays in your workspace** unless you explicitly request deletion via GDPR. ## Step 1: Disconnect on SourceLoop's side 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Chat** in the left sidebar. 3. Find the **Intercom** card under **OAuth chat integrations** and click **Disconnect**. ![SourceLoop Setup Chat page with the Intercom card under OAuth chat integrations showing a Connected status and a red arrow pointing at the Disconnect button at the bottom of the card](/help/screenshots/intercom-disconnect.png) 4. Confirm the disconnect dialog. The moment you confirm: - SourceLoop **revokes the OAuth access token** stored in our database. - SourceLoop **ignores any further webhook events** delivered for this Intercom workspace (Intercom's app-level webhook URL is shared across every workspace that has installed our app, so the URL keeps existing, but events from this disconnected workspace are dropped with a 200 response and no further work). - The Intercom card on the Chat tab flips to **Not connected** with a fresh **Connect** button. No additional Intercom API calls are made by SourceLoop after this point. ## Step 2: Disconnect on Intercom's side (recommended) This step is optional but recommended for completeness. Removing SourceLoop from your Intercom Authorized Apps list: - Provides a paper trail in Intercom's audit log - Prevents accidental reconnect via a cached OAuth flow - Is the cleanest state for GDPR / SOC2 records 1. Sign in to Intercom. 2. Open **Settings (gear icon) -> Apps & Integrations -> Authorized apps**. 3. Find **SourceLoop** in the list. 4. Click the three-dot menu and select **Uninstall**. 5. Confirm. Intercom revokes its end of the OAuth relationship. Even if SourceLoop had any cached token references, they'd be invalid from this moment. ## Step 3: Decide what to do with the sourceloop_id custom attribute During connect, SourceLoop created one custom data attribute on your Intercom contact model: - `sourceloop_id` — used by the Messenger to stitch each visitor to their pre-chat journey After disconnect this attribute **stays on your Intercom contact model**, along with whatever values it last received on individual contacts. SourceLoop does not, and cannot, delete it automatically. You have two options: - **Leave it** (recommended). It doesn't affect Intercom's behaviour, takes no operator attention, and if you reconnect later SourceLoop picks the existing attribute back up automatically, no recreation needed. - **Delete it manually**. Open Intercom **Settings -> Data -> People -> Custom data**, find `sourceloop_id`, and archive or delete it. Note that this also clears the value stored on every contact for that field. ## What SourceLoop retains after you disconnect For transparency, here's exactly what SourceLoop keeps on its side after an Intercom disconnect: ### Deleted at the moment of disconnect - Intercom OAuth access token (encrypted at rest) - Webhook subscription state for this workspace (events from this workspace are now ignored) ### Retained for 30 days, then permanently deleted - The connection record (Intercom workspace id, connection-creation timestamp, last-event timestamp) - Sync logs for the connection (operational debugging, last 30 days) This 30-day grace window exists so that an accidental disconnect can be undone by reconnecting without losing your connection history. After 30 days, the connection record and all associated metadata are purged from our database. Reconnecting after that point creates a fresh connection record. ### Retained indefinitely (until you request deletion) - **Chat conversions captured from Intercom.** Once captured, these conversion records are part of your SourceLoop workspace data. They contain the visitor's email, name, and full attribution journey, governed by your SourceLoop workspace's data policy, not the Intercom connection. - **Contact records and journey timelines** linked to those conversions. - **Aggregated and anonymised analytics** that contributed to dashboards and reports. These retained records can be deleted on request, see "GDPR / full data removal" below. ## GDPR / full data removal If you want SourceLoop to completely remove all data we ever ingested from your Intercom workspace, including conversions, contacts, journeys, and any derived analytics: 1. Email **hello@sourceloop.ai** with the subject "GDPR data deletion request" and your Intercom workspace ID. 2. Confirm your identity (we use the authenticated SourceLoop account email or a verified workspace owner email). 3. We acknowledge the request within 2 business days. 4. We complete the deletion within 30 days and confirm in writing. We'll delete: - The Intercom connection record (if it hasn't been auto-purged already) - All chat conversion records sourced from this Intercom workspace - All contact records, journeys, and timelines tied to those conversions - All sync logs, error logs, and connection history - All custom analytics derived from Intercom-sourced data This is irreversible. After completion, reconnecting Intercom starts from a blank state, the conversions and contacts we deleted cannot be restored. ## Reconnecting later If you change your mind (or accidentally disconnected): 1. Sign in to SourceLoop. 2. Open **Setup -> Chat**. 3. Click **Connect** on the Intercom card. 4. Run the OAuth flow with Intercom. The same scopes are requested. 5. SourceLoop resumes capturing conversations on the next chat. The `sourceloop_id` attribute (and any other SourceLoop-created custom attributes) on your Intercom contact model is picked up automatically; no recreation step needed. If you reconnect within the 30-day window, the connection record is the same one (just reactivated). If you reconnect later, a fresh connection record is created. Either way, the conversions in your SourceLoop Contacts Hub from the previous connection stay intact. ## When to email support For anything outside the standard disconnect / reconnect / GDPR deletion flow: - "I disconnected by accident and want a full reset" → email hello@sourceloop.ai - "We have a data-protection officer review" → we can provide a written data-processing agreement (DPA) and a detailed audit trail of what was retained vs. deleted on your specific connection - "Our Intercom admin says SourceLoop is still listed even though I disconnected" → check the Intercom Authorized Apps page directly; if it's still there, complete Step 2 above Email **hello@sourceloop.ai** for any of the above. We respond within one business day. ## Frequently Asked Questions ### How quickly does SourceLoop stop accessing my Intercom workspace after I disconnect? Immediately. The OAuth access token is revoked on SourceLoop's side at the moment you confirm the disconnect dialog. No further API calls or webhook reads are processed for this workspace from that point on. ### Will my Intercom contacts, tags, or conversations be deleted? No. SourceLoop never deletes data from your Intercom workspace, before, during, or after disconnect. The conversations, contacts, tags, and notes that exist in Intercom stay exactly as they were. ### What about the sourceloop_id custom attribute that SourceLoop created on my contact model? It stays on the Intercom contact model after disconnect, with the values it last received. Removing it requires manual cleanup in Intercom at Settings -> Data -> People -> Custom data. We recommend leaving it; it doesn't affect Intercom's behaviour, and if you reconnect later, the existing attribute gets picked up automatically without needing to be recreated. ### What data does SourceLoop retain on its side after disconnect? Your SourceLoop workspace, Contacts Hub, conversion history, journey timeline, and dashboards stay intact. The Intercom-specific connection record (OAuth token, workspace id, webhook subscription metadata) is kept for 30 days in case you reconnect, after which it's permanently deleted. Captured chat conversions stay in your workspace because they're now part of your SourceLoop dataset. ### How do I request full deletion of my Intercom-sourced data from SourceLoop (GDPR)? Email hello@sourceloop.ai with the subject "GDPR data deletion request" and your Intercom workspace ID. We delete the connection record and all conversions ingested from that Intercom workspace within 30 days. We confirm completion in writing. ### Can I reconnect Intercom later without losing my historical SourceLoop data? Yes. Reconnect at any time via Setup -> Chat -> Intercom -> Connect. SourceLoop's connection record is kept for 30 days, so reconnecting within that window resumes capture seamlessly. Beyond 30 days the connection record is purged, so reconnecting creates a fresh record (your other SourceLoop data, the Contacts Hub, dashboards, journeys, is still intact). ### Does disconnecting affect my SourceLoop subscription or billing? No. Disconnecting Intercom is independent of your SourceLoop subscription. Billing continues per your plan. Other integrations (HubSpot, payment providers, web forms, meetings) are unaffected. --- # How to track lead source in HubSpot Chat Track lead source in HubSpot Chat via the same OAuth connection behind CRM sync. Every chat captures attribution, UTMs, and the prior journey. Source: https://sourceloop.ai/help/track-lead-source-in-hubspot-chat/ Updated: 2026-05-29 --- HubSpot Chat (Conversations) is the conversational layer on top of HubSpot's CRM, every chat creates or updates a contact in your HubSpot portal. What's missing from the native experience is **which marketing channel earned each conversation**, the source data that connects an inbound message back to the campaign that drove it. SourceLoop closes that gap through HubSpot's existing OAuth connection. Every chat that captures an email lands in the SourceLoop Contacts Hub with full first-touch and last-touch attribution, UTMs, landing page, and the entire pre-chat journey, automatically. Two steps, about three minutes. Works on every HubSpot tier. ## What SourceLoop captures from HubSpot Chat For every HubSpot conversation that surfaces an email or phone, SourceLoop attaches: - **First-touch source** (e.g., `google / cpc`, `linkedin / organic`) - **Last-touch source** of the converting session - **UTM parameters** from the landing URL (source, medium, campaign, content, term) - **First-touch landing page** plus the referrer chain - **Pages browsed** before the chat started - **Time on site** and **return-visit count** - **Contact details** from the HubSpot contact record (email, name, phone, company) - **HubSpot conversation ID** for cross-reference back to the chat thread - **Device, country, browser** of the converting session Because chat and CRM share the same HubSpot OAuth connection, the captured contact also flows through the regular [HubSpot CRM sync](/help/connect-hubspot-to-sourceloop/), so SourceLoop's UTM, channel, and journey fields land on the matching HubSpot contact record alongside everything else you track. ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - A **SourceLoop tracker installed** on every page where the HubSpot chat widget appears - A **HubSpot account** (any tier, including Free) with **Conversations** enabled and at least one chatflow live - HubSpot **Super Admin** or **Account Access** permission for the user authorising the connection - **Admin** or **Owner** role in SourceLoop ## Step 1: Connect HubSpot via OAuth If you've already connected HubSpot to SourceLoop for CRM syncing, you only need to **reconnect once** so HubSpot grants the additional `conversations.read` permission. SourceLoop will prompt you on the Chat tab if you do. If you haven't connected HubSpot yet: 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Chat** in the left sidebar. 3. Find the **HubSpot** card under **OAuth chat integrations** and click **Connect**. ![SourceLoop Setup Chat page showing the OAuth chat integrations section with HubSpot and Intercom cards, both with Connect buttons](/help/screenshots/connect-hubspot-intercom.png) 4. You'll be redirected to HubSpot's account picker. Sign in if prompted, select the HubSpot account you want to connect, and click **Choose Account**. ![HubSpot Connecting Sourceloop to HubSpot screen with a Choose an account table listing HubSpot portals and a Choose Account button](/help/screenshots/choose-hubspot-account.png) 5. HubSpot walks you through its consent screen for the requested scopes, then redirects you back to SourceLoop. The card flips to **Connected** with the portal name displayed. What SourceLoop does in the background, automatically: - Stores the OAuth access token and refresh token, encrypted at rest, and rotates tokens as HubSpot's 30-minute access tokens expire - Subscribes to the HubSpot webhook events that fire when a contact is created, when a contact's email is updated, and when a chat conversation is started - Activates the chat capture flow so every conversation started on a tracked page gets stitched to its visitor's pre-chat journey No further configuration needed. Conversations on tracked pages start flowing into SourceLoop on the next chat. > **Already connected HubSpot for CRM sync?** > If your HubSpot card on **Setup -> CRM** is showing Connected but the same card on **Setup -> Chat** says "Reconnect required", click **Reconnect**. This grants the additional `conversations.read` permission HubSpot needs for chat capture. Your existing CRM field mappings, sync schedule, and historical data stay intact. ## Step 2: Verify it's working Open your site in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=hubspot-chat-check` to the URL, click the HubSpot chat widget, and start a real conversation. Share an email you can access (either via HubSpot's pre-chat form, if you have one configured, or mid-conversation when the bot asks for it). Within seconds of the email landing: - A pending chat conversion appears in the **Contacts Hub** at the moment you open the chat, anonymous but with the test UTMs already attached - That row gets enriched with the real email and name as soon as HubSpot fires the contact webhook - The same contact appears in your HubSpot inbox with its SourceLoop-stitched attribution preserved on the linked contact properties (assuming you've also wired the HubSpot CRM sync, which uses the same connection) > **Not seeing the conversation in SourceLoop?** > Open the page with `?sl_debug=1` appended to surface SourceLoop's event log in the browser console. If pageviews are being captured but the chat isn't appearing, confirm the connection card on **Setup -> Chat** is showing **Connected** (not **Pending** or **Reconnect required**), check that the chat widget is loading on the same domain the tracker is installed on, and verify your HubSpot chatflow asks for an email at some point in the conversation. ## How the stitching works A quick read on what's happening under the hood, useful for diagnosing edge cases: | Moment | What SourceLoop does | |---|---| | **Visitor opens the chat widget** | The tracker records a pending Chat conversion with the visitor's anonymous identifier (sourceloop_id), the HubSpot conversation ID, and the full first-touch / last-touch attribution from the visitor's session. | | **Visitor enters their email** | HubSpot fires a webhook for the contact creation (or email property change). SourceLoop fetches the contact, finds the pending Chat conversion by conversation ID, and merges the real email and name into the row. | | **Visitor finishes the conversation** | The conversion row is now fully enriched, real email, attribution, journey, all in one row in the Contacts Hub. | This two-phase stitching is why a chat-attributed lead briefly appears as a pending row before flipping to its real email. If you ever see a stuck `pending-...@hubspot.enrichment` placeholder email, that means HubSpot never fired the contact webhook for that conversation, usually because the visitor never shared an email. ## Where to see HubSpot chats in SourceLoop ### Contacts Hub Every HubSpot conversation that captures an email becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact for the visitor's complete pre-chat journey, every page they viewed, every ad they clicked, every session they had. ![SourceLoop Contacts Hub showing a HubSpot chat lead with the visitor's full pre-chat journey timeline](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the channel-level rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups HubSpot chats by source, medium, and campaign. Useful for spotting which paid campaigns are actually feeding the chat queue with qualified inbound interest. ![SourceLoop attribution dashboard with HubSpot chats grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "HubSpot chat". Slice by source, landing page, or device to find the highest-converting paths from first visit to chat opened. ![SourceLoop funnel report ending in a HubSpot chat conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition feeding the chat queue, forward HubSpot chats back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real qualified conversations rather than vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. Need to disconnect HubSpot? Because Chat and CRM share one OAuth connection, the same flow covers both. See [How to disconnect HubSpot from SourceLoop](/help/disconnect-hubspot-from-sourceloop/) for the full breakdown of what stops, what's retained, and how to request GDPR data removal. ## Frequently Asked Questions ### Do I need to connect HubSpot twice, once for CRM and once for Chat? No. One OAuth connection covers both. If you've already connected HubSpot to SourceLoop for CRM syncing, chat capture starts working as soon as you reconnect once to grant the additional conversations.read permission. If you're connecting for the first time, the single OAuth flow grants both capabilities together. ### Do I need a paid HubSpot plan? No. HubSpot Chat (Conversations) works on every HubSpot tier including Free, and the OAuth connection is the same on every tier. The only requirement is that the HubSpot user authorising the connection has Super Admin or Account Access permission. ### How does SourceLoop know which chat belongs to which visitor? The SourceLoop tracker fires a chat conversion the moment the visitor starts a HubSpot conversation, tagging it with the visitor's sourceloop_id (their anonymous identifier) and the conversation ID. When HubSpot fires the webhook telling us a contact was created or got an email, SourceLoop merges the real email and name into the pending conversion, keeping the full pre-chat attribution intact. ### What's the visitor's journey like before the conversion is finalised? As soon as the visitor opens the HubSpot chat widget on a tracked page, SourceLoop records a pending Chat conversion with the visitor's marketing attribution. When the visitor provides their email (either via HubSpot's pre-chat form or mid-conversation), HubSpot fires a webhook to SourceLoop, which merges the email and name into the pending row. The whole stitch happens within seconds. ### What about visitors who chat but never share an email? They stay anonymous in the SourceLoop Visitors view, with the conversation marked as a chat session but without an identified contact. You can still see the pre-chat journey for everyone in the Visitors view; only contacts who shared an email graduate to the Leads view. ### Does this conflict with HubSpot's native Original Source attribution? No. SourceLoop writes to its own UTM and journey properties on the HubSpot contact (configured in your field mapping), separately from HubSpot's built-in Original Source field. Both can coexist, and SourceLoop's data is multi-touch and per-touchpoint, while HubSpot's Original Source is single-touch only. ### Will my existing HubSpot chatflows, bots, and routing still work? Yes. SourceLoop subscribes to webhooks only and writes back via the standard contact-properties API. Every chatflow, custom bot, routing rule, ticket pipeline, and workflow you have configured continues to fire unchanged. ### How do I disconnect HubSpot Chat? Disconnecting HubSpot removes both CRM and Chat capture at once, since they share one OAuth connection. Open Setup -> CRM or Setup -> Chat in SourceLoop, click Disconnect on the HubSpot card. The OAuth token is revoked immediately and no further webhooks are processed. Previously captured leads stay in your Contacts Hub. --- # How to track lead source in Zendesk Chat Track lead source in Zendesk Chat by piping every Messaging ticket to SourceLoop with the visitor's marketing attribution attached as conversation tags. Source: https://sourceloop.ai/help/track-lead-source-in-zendesk-chat/ Updated: 2026-05-29 --- Zendesk Messaging (and the legacy Web Widget Chat) is the conversation surface inside the Zendesk Suite that established support and sales teams have built their workflow around. The piece it doesn't surface, like every chat tool, is **which marketing campaign produced each conversation**. SourceLoop adds that layer in a semi-automated setup. The tracker attaches your visitor's attribution to every new conversation as Zendesk tags, then a webhook + trigger + hourly automation in Zendesk forwards each ticket to SourceLoop so the conversation becomes a lead with full marketing context. Six steps, around fifteen minutes. Works on every Zendesk Messaging plan. ## What SourceLoop captures from Zendesk Chat For each Zendesk conversation that captures an email, SourceLoop attaches: - **The visitor's acquisition channel** (organic, paid, social, referral, direct) - **UTM parameters** parsed from the landing URL - **First-touch landing page** of the visitor's journey - **Source of the converting session** that produced the chat - **Pages browsed** before the chat started - **Time on site** and **return-visit count** ahead of the conversation - **Email + name + phone** from the Zendesk ticket requester - **Device, country, browser** of the converting session ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to your site's `` markup - A **Zendesk account** on a plan that includes **Triggers**, **Automations**, and **Webhooks** (every paid Zendesk plan and the trial) - **Admin** access to Zendesk Admin Center (required to create webhooks, triggers, and automations) - A **Messaging bot or flow that asks the visitor for their email** at some point in the conversation ## Step 1: Install the SourceLoop tracking script 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Tracking code** in the left sidebar. 3. Copy the snippet shown. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) 4. Paste it into the `` of every page on your site, especially the pages where the Zendesk widget appears. From this point on, every visitor session, UTM, and journey is being recorded. The next steps connect that data to the conversations Zendesk will start sending. ## Step 2: Embed your Zendesk widget on the same pages SourceLoop only attaches attribution when the Zendesk widget loads on a page that also has the SourceLoop tracker. If the widget is already embedded site-wide, you can move on to Step 3. 1. In Zendesk, open **Admin Center -> Channels -> Messaging** (or **Channels -> Classic -> Web Widget** if your account still uses the legacy Chat product). 2. Copy your widget code snippet. It looks like: ```html ``` 3. Paste it inside the `` of your site, on the same pages as the SourceLoop snippet. Once both scripts load on the same page, SourceLoop attaches the visitor's attribution to every new conversation as Zendesk tags. You'll see entries like `sourceloop_id:abc123`, `channel:organic_search`, `landing_page:pricing` in the **Tags** field on the left sidebar of the Agent Workspace when you open any new ticket. > **Tags alone don't create a SourceLoop lead** > Tags are the visitor identity SourceLoop reads when the ticket data arrives. The next three steps wire up the webhook + trigger + automation that actually forward each Zendesk conversation to SourceLoop, where we read the `sourceloop_id:` tag and create the conversion. ## Step 3: Create the Zendesk webhook The webhook is the **delivery channel** for sending data from Zendesk to SourceLoop. You create it once; the next step creates a trigger that uses it. 1. In SourceLoop, open **Setup -> Incoming Webhooks** and copy your **Incoming Webhook URL** for chat. ![SourceLoop Setup Incoming Webhooks page with the chat webhook URL ready to copy](/help/screenshots/zendesk-sourceloop-incoming-webhook.png) 2. In Zendesk, open **Admin Center -> Apps and integrations**. ![Zendesk Admin Center Apps and integrations page](/help/screenshots/zendesk-apps-and-integrations.png) 3. Click **Webhooks** in the left sidebar, then **Create webhook** in the center. ![Zendesk Webhooks list view with the Create webhook button highlighted](/help/screenshots/zendesk-webhooks-create.png) 4. On **Select a way to connect**, pick **Trigger or automation** on the right. Click **Next**. ![Zendesk webhook Select a way to connect screen with Trigger or automation option highlighted](/help/screenshots/zendesk-webhook-select-connect.png) 5. On **Add details**, fill in: - **Name**: `SourceLoop` - **Description**: leave blank or write "SourceLoop attribution" - **Endpoint URL**: paste the URL you copied from SourceLoop in step 1 - **Request method**: `POST` - **Request format**: `JSON` - **Authentication**: `None` ![Zendesk webhook Add details form filled in with Name SourceLoop, Endpoint URL, POST method, JSON format, and Authentication None](/help/screenshots/zendesk-webhook-add-details.png) 6. Click **Create webhook**. Zendesk will offer to send a test webhook, you can skip it. The webhook now exists but is inert until something sends data to it. Move to Step 4. ## Step 4: Create the trigger that fires on new conversations The trigger is what hands every new Messaging ticket to the webhook in real time. 1. In Zendesk, open **Admin Center -> Objects and rules -> Business rules -> Triggers**. 2. Click **Add trigger** in the top right. 3. Fill in the basics: - **Trigger name**: `Send to SourceLoop` - **Description**: `Forward every new Messaging conversation to SourceLoop attribution` - **Category**: pick any (or create one named "SourceLoop") 4. Under **Conditions -> Meet ALL of the following conditions**, click **Add condition** and set: - **Ticket > Channel** | **Is** | **Messaging** - **Ticket > Ticket** | **Is** | **Created** 5. Under **Actions**, click **Add action** and set: - **Notify by** | **Active webhook** | **SourceLoop** - Paste this JSON body: ```json { "chat_id": "zendesk:{{ticket.id}}", "email": "{{ticket.requester.email}}", "name": "{{ticket.requester.name}}", "phone": "{{ticket.requester.phone}}", "tags": "{{ticket.tags}}", "subject": "{{ticket.title}}", "type": "Chat" } ``` 6. Click **Create**. From now on, every new Messaging ticket fires this trigger at creation time, and Zendesk POSTs the ticket details (including the `sourceloop_id:` tag) to SourceLoop. > **Don't add an Add tags action** > The `chat_id` field makes multiple fires safe (SourceLoop dedups on it), and adding a "sent to SourceLoop" tag here would prevent the hourly automation in Step 5 from firing for late-email tickets. ## Step 5: Add an automation to catch late-email tickets The trigger from Step 4 fires the moment a Messaging ticket is created. But Zendesk Messaging usually asks for the visitor's **name** first and **email** a step or two later, so the ticket often exists before the email arrives. Zendesk does not re-fire the trigger when the visitor later adds their email (because email lives on the user record, not the ticket). This automation runs hourly and re-sends the ticket to SourceLoop. SourceLoop ignores any fire that still has no email (no half-empty leads appear in your Contacts Hub) and dedups identical fires by `chat_id`. As soon as one of those hourly fires lands with the email present, the conversion is created. 1. In Zendesk, open **Admin Center -> Objects and rules -> Business rules -> Automations**. 2. Click **Create automation** in the top right. ![Zendesk Admin Center Automations list with the Create automation button highlighted](/help/screenshots/zendesk-automations-create.png) 3. Fill in the basics: - **Automation name**: `Send to SourceLoop (catch-up)` - **Description**: `Hourly backstop for Messaging tickets where email arrives after creation` 4. Under **Conditions -> Meet ALL of the following conditions**, click **Add condition** three times and set: - **Ticket > Status category** | **Is** | **Open** - **Ticket > Channel** | **Is** | **Messaging** - **Ticket > Hours since created** | **Is** | **1** ![Zendesk automation conditions block with three rows: Status category Is Open, Channel Is Messaging, Hours since created Is 1](/help/screenshots/zendesk-automation-conditions.png) 5. Under **Actions**, click **Add action** and set: - **Notify by** | **Active webhook** | **SourceLoop** - Paste the **same JSON body** as the trigger in Step 4: ```json { "chat_id": "zendesk:{{ticket.id}}", "email": "{{ticket.requester.email}}", "name": "{{ticket.requester.name}}", "phone": "{{ticket.requester.phone}}", "tags": "{{ticket.tags}}", "subject": "{{ticket.title}}", "type": "Chat" } ``` ![Zendesk automation Actions panel with Notify by Active webhook SourceLoop and the JSON body payload](/help/screenshots/zendesk-automation-notify-action.png) 6. Click **Create automation**. > **Trade-off worth knowing** > Zendesk automations run on a roughly hourly cycle and can't be sped up on the trial plan. Late-email Messaging tickets land in SourceLoop 1 to 2 hours after the chat starts, not in real time. Real-time capture only happens when the visitor shares their email during the conversation that creates the ticket. ## Step 6: Run a verification chat Open your site in an **incognito tab** with `?utm_source=test&utm_medium=verify&utm_campaign=zendesk-check` appended to the URL. Click the Zendesk widget, start a conversation, and share an email you can access. If your Messaging flow asks for the email **during** the first conversation, the lead should appear in the **Contacts Hub** within seconds. If the bot asks for the email later or never during that session, wait up to two hours for the hourly automation to catch up. > **Not seeing the conversation in SourceLoop?** > Open the Zendesk ticket in your Agent Workspace and check the **Tags** field on the left sidebar. You should see `sourceloop_id:...`, `channel:...`, `landing_page:...` entries. If they're missing, the SourceLoop tracker isn't running on the page where the chat started, double-check Step 1. If the tags are there but the lead isn't, check the Zendesk webhook delivery log at **Admin Center -> Apps and integrations -> Webhooks -> SourceLoop -> Activity** for errors. ## Where to see Zendesk conversations in SourceLoop ### Contacts Hub Every Zendesk conversation that captures an email becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). Open a contact for the visitor's complete pre-chat browsing history, useful context for the agent (or bot) before they respond. ![SourceLoop Contacts Hub showing a Zendesk Chat conversation lead with the visitor's full pre-chat journey](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard For the channel-level rollup, [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) groups Zendesk conversations by source, medium, and campaign so you can see which marketing channels actually open dialogue with real prospects vs. which only generate pageviews. ![SourceLoop attribution dashboard with Zendesk Chat conversations grouped by source and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) ### Funnel reports Build a funnel at [app.sourceloop.ai/funnels](https://app.sourceloop.ai/funnels) ending in "Zendesk Chat conversation". Slice by source, landing page, or device to find the highest-converting routes from first visit to chat opened. ![SourceLoop funnel report ending in a Zendesk Chat conversion step](/help/screenshots/sourceloop-funnel.png) For paid acquisition feeding the chat queue, mirror Zendesk conversations back to **Google Ads, Meta, and LinkedIn as offline conversions** so the bidding algorithms train on real qualified conversations instead of vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with Zendesk Messaging (the modern widget) and the legacy Web Widget Chat? Yes, both. The setup is the same. Modern Messaging accounts use Admin Center -> Channels -> Messaging for the widget; older accounts still on the classic Chat product use Channels -> Classic -> Web Widget. Either widget exposes the same tagging API SourceLoop uses to attach attribution to the conversation. ### Why do I need to set up a webhook plus a trigger plus an automation? Can't this be one piece? Three reasons. The webhook is just the URL Zendesk POSTs to. The trigger is what fires on ticket creation (the real-time path). The automation is a backstop because Zendesk Messaging tickets often get created BEFORE the visitor has shared their email, and Zendesk doesn't re-fire the trigger when the email lands later. The hourly automation re-sends the ticket once email is present. ### How does the late-email backstop avoid creating duplicate leads? Every fire (real-time trigger and hourly automation) carries the same `chat_id` (Zendesk's ticket ID prefixed with `zendesk:`). SourceLoop dedups on `chat_id`, so multiple fires for the same ticket land as a single lead. Fires that arrive without an email are silently ignored (no half-empty leads appear in your Contacts Hub). ### How long after a Messaging chat does a late-email lead appear in SourceLoop? Real-time if the visitor shares their email during the conversation that creates the ticket. 1 to 2 hours if the email arrives after ticket creation, because Zendesk automations run on a roughly hourly cycle and that cadence can't be sped up on the trial plan. ### My Messaging bot only asks for a name, not an email. Will SourceLoop still capture the lead? No. SourceLoop creates a lead only when an email (or phone number) is present. Open your Messaging flow in Admin Center -> Channels -> Messaging, find the answer/flow used at conversation start, and add a required Email step. Without one, the visitor stays anonymous and no lead is created. ### Conversations from a Zendesk-hosted Help Center page, not my main site. Do those get attribution? No. The marketing journey (UTMs, channel, landing page) lives in the SourceLoop tracker on your own site. If a visitor opens the chat from a Zendesk-hosted Help Center page (yourbrand.zendesk.com/...), the tracker never runs on that page and there's no journey to attach. Route paid campaigns to pages on your own domain where the widget appears. ### Will my existing Zendesk triggers, automations, and CRM integrations keep working? Yes. The trigger and automation you create for SourceLoop sit alongside everything else you have set up. SourceLoop receives its own copy of the ticket data via the webhook; every other Zendesk destination (Salesforce, HubSpot, Slack alerts, etc.) continues to receive ticket events unchanged. ### I support Zendesk via the mobile SDK in my app. Are mobile chats tracked? Web sessions are the scope of SourceLoop's marketing-source data. Mobile-app conversations through Zendesk's SDK don't have a browser journey to attach, so they arrive as Direct. --- # How to set up marketing attribution tracking for Shopify Tie every Shopify order back to the ad, campaign, and channel that drove it. Real revenue attribution, ROAS and AOV by source, no theme.liquid edits. Source: https://sourceloop.ai/help/marketing-attribution-for-shopify/ Updated: 2026-07-03 --- Shopify is excellent at running checkout and recording who paid. What it cannot tell you is **which marketing channel produced each order**, which is the question that actually matters when you are deciding where next month's ad budget goes. SourceLoop closes that loop. Every order, every repeat purchase, every refund mapped back to the shopper's original source, so "we did $X in revenue" becomes "we did $X, most of it from the Meta prospecting campaign and organic search, with the retargeting set barely breaking even." Shopify uses its own **connect flow**, not the manual tracking snippet. Four steps, around ten minutes, no theme edits. ## Why Shopify needs a separate path On a normal website you paste the SourceLoop snippet into the page `` yourself. Shopify is different in two ways that make a dedicated flow the right approach: - **Storefront tracking is a toggle, not code.** Rather than editing `theme.liquid` (which breaks on theme updates and is fragile across Shopify 2.0 sections), you turn on a single **app embed** in your theme editor. It is a switch, not a snippet, so there is nothing to paste and nothing to maintain. - **Orders sync straight from Shopify.** Instead of reading a payment webhook you configure by hand, SourceLoop connects to your store over Shopify's official app authorization and receives orders, customers, and checkouts directly, then stitches each one to the visitor session that drove it. The result is the same as every other SourceLoop integration, full source-to-revenue attribution, but with a setup built specifically for how Shopify apps are meant to work. For an overview of what [ecommerce tracking](/features/ecommerce-tracking/) covers across stores and email platforms, see the feature page. ## What SourceLoop captures from Shopify Each Shopify order arrives in SourceLoop tied to: - **Acquisition channel** of the shopper at the session that led to the order (paid, organic, social, referral, direct) - **UTM parameters** and ad click IDs from the landing URL of the converting journey - **First-touch landing page** plus the original referrer - **Pages and products browsed** before checkout, plus time on site and return-visit count - **Email and name** from Shopify's customer record - **Full order economics**, order value, line items, discounts, currency, and order status - **Repeat purchase and subscription history**, so LTV rolls up by original source - **Refunds and cancellations**, netted against the original order - **Device, country, browser** ## Before you start You will need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - A **Shopify store** where you are the owner or a staff member with permission to install apps - A few minutes at your storefront to place a test order at the end ## Step 1: Open the Shopify card in SourceLoop Sign in to SourceLoop and open **Setup -> E-commerce** in the left sidebar. You'll see the **Shopify** card ("Track purchase revenue and attribute orders to ad clicks"). Click it. ![SourceLoop Setup page with the E-commerce tab selected in the left sidebar and the Shopify card in the main panel](/help/screenshots/sourceloop-shopify-ecommerce-tab.webp) ## Step 2: Connect your store A drawer slides in from the right with a **Store domain** field and a **Connect** button, followed by a short setup checklist. ![SourceLoop Shopify drawer showing the Store domain field with a my-store.myshopify.com placeholder, a Connect button, and a three-step setup checklist: connect your store, turn on the app embed, and you're all set](/help/screenshots/sourceloop-shopify-connect-drawer.webp) 1. Enter your store domain (for example `your-brand.myshopify.com`) and click **Connect**. 2. SourceLoop hands you to Shopify's official app authorization screen, where Shopify lists what SourceLoop is requesting: **read access to orders, customers, and checkouts**. It requests **read access only**, and never asks to modify products, prices, inventory, or fulfillment. 3. Click **Install app** to approve. Shopify returns you to SourceLoop automatically. > **Also run a separate marketing site?** > If your ads and landing pages live on a different domain (a blog, a campaign microsite), install the standard [tracking snippet](/help/install-the-tracking-pixel/) there as well. That way a journey that starts on your marketing site and finishes at Shopify checkout is captured end to end. ## Step 3: Turn on the app embed For storefront tracking to run, switch on SourceLoop's app embed in your theme. This is a **toggle, not a code edit**: 1. In your **Shopify admin**, go to **Online Store -> Themes -> Customize**. 2. Open the **App embeds** panel (the puzzle-piece icon in the theme editor sidebar). 3. Toggle on **Sourceloop Attribution**, then click **Save**. ![Shopify theme editor App embeds panel with the Sourceloop Attribution embed toggled on, alongside a live storefront preview](/help/screenshots/shopify-app-embed-toggle.png) That's it, no `theme.liquid` editing and nothing to paste. Because it's an app embed, it survives theme updates and Shopify 2.0 section changes. From here on, every storefront visitor's session, UTM, referrer, and browsing journey is recorded, ready to be stitched onto the order they place. ## Step 4: Place a test order to verify Open your storefront in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=shopify-check` to the URL, browse a product, and complete a checkout. Shopify's own **Bogus Gateway** or test-mode payments let you do this without moving real money. Within seconds of Shopify confirming the order, the customer should appear at the top of the **Contacts Hub** in SourceLoop with the test UTMs attached and a revenue event linked to the same contact. > **Not seeing the order?** > In the Shopify drawer, check the **Last event** timestamp. If it is stale, Shopify has not delivered the order yet, give it a moment and refresh. If events are arriving but the contact is not linking, the usual cause is a checkout where the shopper never browsed the storefront first (for example a direct Shopify checkout link), which leaves no session to stitch to. Those still import, attributed as Direct. ## Where to see Shopify revenue in SourceLoop ### Contacts Hub: revenue per customer Every Shopify customer becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with a **revenue column** showing what they have spent to date. Click a contact to expand the full order ledger, first order, every repeat purchase, every refund, next to the shopper's pre-purchase journey. Sort or segment the hub by revenue to find your highest-value customers, or by source to see which channels bring in buyers versus browsers. ![SourceLoop Contacts Hub showing a Shopify customer with the revenue column, full pre-purchase journey, and order history](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard: orders and revenue by channel [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) breaks your storefront traffic down by **Channel, Source, Medium, Campaign, UTM Term, UTM Content, and Referrer**, with conversions and conversion rate on every row. So instead of "Meta drove 900 sessions," you see which channels actually turn into orders. ![SourceLoop attribution dashboard showing the Channel breakdown table with visitors, sessions, pageviews, conversions, and conversion rate per channel, and tabs for Source, Medium, Campaign, UTM Term, UTM Content, and Referrer](/help/screenshots/sourceloop-traffic-dashboard-table.webp) Switch attribution models from the dropdown at the top to see how credit shifts: - **Last Non-Direct** (the default, matches Google Analytics) - **First Touch** / **First Non-Direct** for top-of-funnel weighting - **Last Touch** for closing-channel weighting - **Linear**, **Position-Based (U-Shaped)**, and **Time Decay** for multi-touch views ### Paths dashboard: the full journey to each order Most Shopify orders take more than one touch. [app.sourceloop.ai/dashboards/paths](https://app.sourceloop.ai/dashboards/paths) lays out the actual **conversion paths** (for example `social -> paid_search`) with **visitors, conversions, and attributed revenue** on each path, so you can see which channel combinations drive the most store revenue, not just which one gets last-click credit. ![SourceLoop Paths dashboard listing multi-touch conversion paths by channel with visitors, conversions, and an attributed revenue column in dollars](/help/screenshots/sourceloop-paths-dashboard.webp) Once Shopify revenue is flowing, push it back to **Google Ads, Meta, and TikTok as conversions with order values** so the bidding algorithms optimise toward real buyers, not add-to-carts. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. When you are ready to remove the integration, or need to answer a data-protection question about it, see [What Shopify data SourceLoop stores and deletes](/help/shopify-data-deletion/). ## Frequently Asked Questions ### Do I need to edit my theme.liquid or paste any code? No code. Shopify uses a dedicated connect flow, not the manual snippet. After you approve access, you flip on a single app-embed toggle ("Sourceloop Attribution") in your theme editor. It is a switch, not a code edit, so theme updates and Shopify 2.0 sections do not break it, and there is nothing to paste into theme.liquid. ### Why do my Shopify orders arrive without a source today? Shopify only sees the checkout, not the shopper's journey. By the time someone clicks an ad, browses, adds to cart, and checks out (often days later from a different device), the original UTM is long gone from the URL. SourceLoop captures the source on the first page view, persists it across the whole buying journey, and stitches it onto the order the moment it is placed. ### Are repeat purchases and subscriptions attributed back to the first source? Yes. Source is tied to the customer, not just the first order. Repeat purchases, Recharge and other subscription renewals all roll up under the original acquisition channel, so AOV, LTV, and repeat-rate reports stay tied to the channel that earned the customer. ### Does this replace the pixel I install on non-Shopify sites? For your Shopify storefront, the app handles storefront tracking for you, so you do not add the manual snippet there. If you also run a marketing site, blog, or landing pages on a separate domain, install the standard tracking snippet on those pages so pre-checkout journeys that start off-Shopify are captured too. ### What Shopify permissions does SourceLoop request, and can I revoke them? Read access to orders, customers, and checkouts, so it can match each sale to a visitor session and attach source data. It never requests write access to your catalog, prices, or fulfillment. You can revoke access any time from Shopify Settings, Apps and sales channels, by uninstalling SourceLoop, which immediately stops all access. ### How is refund and cancellation revenue handled? Refunds and cancellations flow in as negative revenue tied back to the original order and source. The attribution dashboard nets them against the channel that drove the sale, so reported channel revenue stays accurate even weeks after the order. ### Where does the imported data go, and what happens if I uninstall? Order and customer data synced from Shopify lives only in your own SourceLoop workspace. Uninstalling the app revokes access immediately and triggers our data-deletion process. See the companion guide, What Shopify data SourceLoop stores and deletes, for exactly what is removed and when. --- # How to track lead source in Stripe Tie every Stripe charge, subscription, and renewal back to the channel that drove it. Real revenue attribution and MRR by source, not contact counts. Source: https://sourceloop.ai/help/track-lead-source-in-stripe/ Updated: 2026-05-28 --- Stripe is brilliant at running checkout and recording who paid. What it can't tell you is **which marketing channel produced each dollar of MRR**, which is the question that actually matters when you're deciding where to spend next quarter's budget. SourceLoop closes that loop. Every charge, every renewal, every refund mapped back to the visitor's original source, so "we made $X" becomes "we made $X, with most of it from organic SEO and the Reddit campaign breaking even". Four steps, around ten minutes. Works on every Stripe plan, every checkout surface. ## Why this matters Most attribution stops at the signup or the trial start. That works for one-off purchases. For SaaS and any subscription business, the real revenue lives downstream of the first event: - A user signs up in June via a Google Ads campaign - They start a free trial, then convert to a $99/mo plan in July - They upgrade to a $199/mo plan in October - They renew every month for two more years Without revenue tracking, Google Ads gets credit for one "trial signup" and never sees the $5K+ of lifetime revenue that flowed from it. With Stripe wired into SourceLoop, every renewal, upgrade, and downgrade hangs off the same lead, and channel-level reporting reflects actual MRR, not just first-touch counts. ## What SourceLoop captures from Stripe Each Stripe customer arrives in SourceLoop tagged with: - **Acquisition channel** of the visitor at signup (paid, organic, social, referral, direct) - **UTM parameters** from the landing URL of the converting session - **Pages browsed** before the checkout was started - **Time on site** and **return-visit count** ahead of the purchase - **Email + name** from Stripe's customer record - **First-touch landing page** plus the original referrer - **Source of the converting session** (often distinct from first-touch) - **Full revenue history**, one-off charges, subscription created / upgraded / downgraded / paused / canceled, recurring invoices (renewals), refunds and chargebacks - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the site that hosts your checkout button or Payment Link - A **Stripe account** with a Checkout Session, Payment Link, or Payment Intent flow live - **Developer or owner access** to your Stripe Dashboard, you'll need it to add a webhook endpoint ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup -> Tracking code** in the sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into the `` of every page on your site, especially the pages that lead into Stripe checkout. From this point on, every visitor session, UTM, and journey is being recorded. The next steps connect that data to the payments Stripe will start sending. ## Step 2: Generate a webhook in your Stripe Dashboard Stripe sends payment events as webhooks. SourceLoop gives you a dedicated webhook URL, you paste it into Stripe, and Stripe sends each event there alongside any other destinations you've already configured. 1. In SourceLoop, open **Setup -> Payment** in the sidebar. You'll see all supported payment providers (Stripe, Lemon Squeezy, Paddle, Polar, Dodo) listed as cards. ![SourceLoop Setup Payment page showing the supported payment provider cards](/help/screenshots/sourceloop-payment-page.png) 2. Click the **Stripe** card. A drawer opens on the right with two tabs: **Connect webhook** and **Wire attribution**. Stay on the first tab and copy the **webhook URL** SourceLoop shows you. ![SourceLoop payment provider drawer with the webhook URL and signing secret fields](/help/screenshots/sourceloop-payment-webhook-drawer.webp) 3. Open the [Stripe Dashboard](https://dashboard.stripe.com/), navigate to **Developers -> Webhooks**, and click **Add endpoint**. 4. Paste SourceLoop's URL into the **Endpoint URL** field. 5. Under **Select events**, choose the events you want SourceLoop to receive. The recommended set: `charge.succeeded`, `charge.refunded`, `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.paid`. 6. Click **Add endpoint** to save. > **Already have a Stripe webhook to your app?** > You can add SourceLoop as an additional endpoint, no need to replace your existing one. Stripe delivers each event to every endpoint independently. ## Step 3: Paste the signing secret back into SourceLoop Stripe verifies every webhook with a signing secret. You'll find it on the endpoint detail page you just created. 1. On the endpoint detail page in Stripe, find **Signing secret** and click **Reveal**. 2. Copy the `whsec_...` value. 3. Back in the same SourceLoop drawer, paste it into the **Webhook signing secret** field and click **Save**. SourceLoop's connection status flips from **pending** to **active** the first time Stripe sends a verified event through. ## Step 4: Wire attribution into your checkout The webhook tells SourceLoop **a payment happened**. The attribution wiring tells SourceLoop **which visitor session** that payment belongs to. Without it, Stripe events still flow in, but stitching falls back to matching by customer email, lower fidelity, especially for one-off purchases where the customer signs up at checkout. Once the tracker is installed, it exposes a small helper on every page that returns the two stitching identifiers for the current visitor: ```js window.sourceloop.checkoutMetadata() // returns { sourceloop_anonymous_id: '...', sourceloop_id: '...' } ``` Pass that object through to Stripe in whichever way matches your checkout surface. Use this guide to pick: - **(1) Stripe Checkout** — The default for most businesses. If clicking **Buy**, **Subscribe**, **Upgrade**, **Checkout**, or **Pay** redirects the customer to a Stripe-hosted page on `checkout.stripe.com`, this is you. Covers SaaS subscriptions, one-off products, digital downloads, course sales, donations, anything that hands the customer off to Stripe's hosted checkout. - **(2) Payment Intents and Subscriptions API** — When you render the card form yourself with Stripe Elements (no redirect, the form lives inside your app). Picked by custom checkouts, white-labeled flows, native mobile apps, and marketplaces that need full control over the checkout UX. - **(3) Payment Links** — Static `buy.stripe.com/...` URLs you paste into tweets, marketing pages, email broadcasts, or affiliate links, no per-customer code creating the session. Lowest fidelity for attribution. If you use more than one (e.g., Checkout for first-time signups and Payment Intents for in-app upgrades), wire each one. ### (1) Stripe Checkout (hosted checkout sessions) The most common pattern. Capture the metadata on the client, send it to your backend with the price id, and set it on the Checkout Session when you create it. For subscription mode, also persist it onto `subscription_data.metadata` so every renewal event stays stitched. Client: ```js const meta = window.sourceloop.checkoutMetadata(); const res = await fetch('/api/create-checkout', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ priceId: 'price_xxx', meta }), }); const { url } = await res.json(); window.location.href = url; ``` Server (Node.js): ```js const session = await stripe.checkout.sessions.create({ mode: 'subscription', line_items: [{ price: priceId, quantity: 1 }], success_url: 'https://your.site/thanks', cancel_url: 'https://your.site/pricing', // Attach to the Checkout Session (read by charge events) metadata: meta, // Persist onto the Subscription itself (read by every renewal) subscription_data: { metadata: meta }, }); return Response.json({ url: session.url }); ``` ### (2) Payment Intents and Subscriptions API For custom Stripe Elements flows. Set `metadata` when you create the `PaymentIntent` or `Subscription` server-side. Subscription metadata is the strongest signal, it propagates to every future renewal event automatically. One-shot PaymentIntent: ```js const intent = await stripe.paymentIntents.create({ amount: 9900, currency: 'usd', customer: customerId, metadata: meta, }); ``` Recurring Subscription: ```js const sub = await stripe.subscriptions.create({ customer: customerId, items: [{ price: priceId }], metadata: meta, }); ``` ### (3) Payment Links (buy.stripe.com) Static "Buy" buttons from `buy.stripe.com`. Append `client_reference_id` to every Buy URL before the visitor clicks, Stripe stores it on the resulting Checkout Session, and SourceLoop reads it from the webhook event. ```html [Subscribe](https://buy.stripe.com/your_link) ``` > **Payment Links are the lowest-fidelity option** > `client_reference_id` only carries the anonymous-id half of the stitching pair, not the full session id. If it's missing, SourceLoop falls back to matching by customer email. Use Checkout Sessions instead of Payment Links wherever you can. > **Let your AI assistant do the wiring** > If you'd rather not patch every Stripe integration point by hand, paste the snippets above into Cursor, Claude Code, or your IDE assistant with the prompt "Apply these Sourceloop attribution patterns to every Stripe integration in this codebase. Show me the files you plan to touch before editing." It'll find every `stripe.checkout.sessions.create`, `stripe.paymentIntents.create`, and Buy URL on the site, and patch them in one pass. ## Step 5: Run a test charge to verify Open the page with your Stripe checkout in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=stripe-check` to the URL, and complete a real card transaction. Stripe test mode is the safest path if you'd rather not move real money, just toggle your endpoint to **Listen to events on your test account** while testing. Within seconds of Stripe confirming the charge, the customer should appear at the top of the **Contacts Hub** in SourceLoop with the test UTMs attached and a revenue event linked to the same contact. > **Not seeing the event?** > Inside SourceLoop, open the Stripe connection card and check the **Last event** timestamp. If it's stale, the issue is on Stripe's webhook delivery side, check Stripe Dashboard -> Webhooks -> Logs for delivery errors. If the event is being received but the contact isn't linking, the most common cause is the customer's email not matching their pre-checkout session, which usually means the visitor never browsed your site before paying. ## Where to see Stripe revenue in SourceLoop ### Contacts Hub: revenue per contact Every Stripe customer becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with a **revenue column** showing what they've paid you to date. Click a contact to expand the full revenue ledger, original charge, every renewal, every upgrade, every refund, alongside the visitor's pre-purchase journey. Filter, sort, or segment the hub by revenue to surface your highest-value customers, or by source to see which channels are bringing in paying customers vs. just signups. ![SourceLoop Contacts Hub showing a Stripe customer with the revenue column, full pre-purchase journey, and revenue history](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard: revenue by channel [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) adds a **revenue column** alongside the breakdowns you already see: source, medium, campaign, landing page, content, term, device, country. So instead of "Google CPC drove 240 sessions", you see "Google CPC drove 240 sessions and $8,400 in attributed Stripe revenue." Switch attribution models from the dropdown at the top of the dashboard to see how the numbers shift: - **Last Non-Direct** (the default, matches Google Analytics) - **First Touch** / **First Non-Direct** for top-of-funnel weighting - **Last Touch** for closing-channel weighting - **Linear** to split credit evenly across every touchpoint - **Position-Based (U-Shaped)** for 40% first / 40% last / 20% middle - **Time Decay** to weight recent touches higher (7-day half-life) Different models surface different stories. A campaign that looks weak under Last Touch may be your biggest top-of-funnel driver under First Touch, that's the value of being able to flip between them in one click. ![SourceLoop attribution dashboard with Stripe revenue column grouped by source, medium, and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) Once Stripe revenue is flowing, push it back to **Google Ads, Meta, and LinkedIn as offline conversions with revenue values** so the bidding algorithms optimise toward real paying customers, not trial signups or vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with Stripe Checkout, Payment Links, and Payment Intents? Yes. All three checkout surfaces are covered. The webhook setup is the same for all of them, the only difference is in step 4, where you wire attribution metadata. The Attribution tab inside the Stripe drawer ships a snippet for each surface. ### What if I use more than one Stripe checkout surface (Checkout for sales, Payment Intents for in-app upgrades)? Wire all of them. Each one gets its own attribution snippet. SourceLoop reads the same metadata keys regardless of which surface produced the event, so a customer who upgrades later via Payment Intents still ties back to their original Stripe Checkout signup. ### Are subscription renewals attributed back to the original source? Yes. This is the whole point. Every renewal invoice gets stitched onto the same contact that started the subscription, so MRR and LTV stay tied to the channel that earned the original signup, not just the first charge. ### How do refunds and chargebacks show up? Refunds and chargebacks flow into SourceLoop as negative revenue tied back to the original lead. The attribution dashboard nets them against the original source, so reported channel revenue stays accurate even months after the fact. ### I run a freemium product where signups happen first and charges happen later. Does that still tie back? Yes, that's the most common SaaS pattern. The free signup creates the contact in SourceLoop with full attribution. When the user upgrades to paid weeks or months later, the Stripe event ties to the same email and attaches revenue to the original source. ### Will my existing Stripe webhooks to Zapier, HubSpot, or my data warehouse still fire? Yes. SourceLoop receives its own copy of Stripe events via its own webhook endpoint. Every other destination you have configured continues to receive Stripe events independently. ### Is my Stripe webhook signing secret safe? Yes. The secret is encrypted at rest the moment you save it, used only on SourceLoop's backend to verify webhook authenticity, and never exposed in the browser. You can rotate or revoke it in Stripe whenever you want. --- # How to track lead source in Lemon Squeezy Tie every Lemon Squeezy order, subscription, and renewal back to the channel that drove it. Real revenue attribution for SaaS and digital products. Source: https://sourceloop.ai/help/track-lead-source-in-lemonsqueezy/ Updated: 2026-05-28 --- Lemon Squeezy is the merchant-of-record platform indie founders and SaaS teams reach for when they want a checkout that handles global tax automatically. The plumbing is great. What it doesn't surface in its analytics is **which marketing channel earned each subscriber**, which is the number that decides where next month's ad spend goes. SourceLoop closes that gap. Every order, renewal, and refund mapped back to the visitor's first session, so "Lemon Squeezy says we made $X" becomes "we made $X, and ProductHunt brought in half of it for free while the affiliate program barely covered the payouts". Five steps, around ten minutes. Works on every Lemon Squeezy store. ## Why this matters Lemon Squeezy's strength is recurring revenue, and recurring revenue is exactly where most attribution tools fall apart. Picture the standard SaaS path: - Someone discovers your product via a tweet in March - They sign up for the $19/mo plan - They upgrade to $49/mo in June - They keep renewing every month for two more years Without revenue tracking, the tweet gets credit for a single "checkout completed" event and never sees the $1,200+ of lifetime value that flowed from it. With Lemon Squeezy wired into SourceLoop, every renewal, every upgrade, every cancellation hangs off the same lead, and channel-level reporting reflects actual MRR, not just first-touch counts. ## What SourceLoop captures from Lemon Squeezy Each Lemon Squeezy customer arrives in SourceLoop tagged with: - **Acquisition channel** of the visitor at signup (paid, organic, social, referral, direct) - **UTM parameters** from the landing URL of the converting session - **Pages browsed** before the checkout was opened - **Time on site** and **return-visit count** ahead of the purchase - **Email + name** from Lemon Squeezy's customer record - **First-touch landing page** plus the original referrer - **Source of the converting session** (often distinct from first-touch) - **Full revenue history**, one-off orders, subscription created / updated / canceled / resumed, recurring payment success (renewals), refunds - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the site that hosts your Lemon Squeezy Buy URL or API-created checkout - A **Lemon Squeezy store** with at least one variant published - **Admin access** to your Lemon Squeezy Dashboard, you'll need it to add a webhook ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup -> Tracking code** in the sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into the `` of every page on your site, especially the pages with your Lemon Squeezy Buy buttons or overlay triggers. From this point on, every visitor session, UTM, and journey is being recorded. The next steps connect that data to the orders Lemon Squeezy will start sending. ## Step 2: Create a webhook in your Lemon Squeezy Dashboard Lemon Squeezy sends order and subscription events as webhooks. SourceLoop gives you a dedicated webhook URL, you paste it into Lemon Squeezy, and Lemon Squeezy starts delivering events. 1. In SourceLoop, open **Setup -> Payment** in the sidebar. The payment provider cards are listed. ![SourceLoop Setup Payment page showing the supported payment provider cards](/help/screenshots/sourceloop-payment-page.png) 2. Click the **Lemon Squeezy** card. A drawer opens on the right with two tabs: **Connect webhook** and **Wire attribution**. Stay on the first tab and copy the **webhook URL** SourceLoop shows you. ![SourceLoop payment provider drawer with the webhook URL and signing secret fields](/help/screenshots/sourceloop-payment-webhook-drawer.webp) 3. Open the [Lemon Squeezy Dashboard](https://app.lemonsqueezy.com/), navigate to **Settings -> Webhooks**, and click **Create webhook**. 4. Paste SourceLoop's URL into the **Callback URL** field. 5. Under **Events**, enable all subscription and order events. The recommended set: `order_created`, `subscription_created`, `subscription_updated`, `subscription_cancelled`, `subscription_resumed`, `subscription_expired`, `subscription_payment_success`, `subscription_payment_refunded`. 6. Set a **Signing secret** (any string you choose, treat it like a password) and save the webhook. > **Already have a webhook to your own backend?** > You can add SourceLoop as an additional webhook, no need to replace the one you already have. Lemon Squeezy delivers events to every webhook in parallel. ## Step 3: Paste the signing secret back into SourceLoop Lemon Squeezy verifies every webhook with the signing secret you just chose. 1. Copy the signing secret you set in the previous step. 2. Back in the same SourceLoop drawer, paste it into the **Webhook signing secret** field and click **Save**. SourceLoop's connection status flips from **pending** to **active** the first time Lemon Squeezy sends a verified event through. ## Step 4: Wire attribution into your checkout The webhook tells SourceLoop **a payment happened**. The attribution wiring tells SourceLoop **which visitor session** that payment belongs to. Without it, events still flow in, but stitching falls back to matching by customer email, lower fidelity, especially for one-off purchases where the customer signs up at checkout. Once the tracker is installed, it exposes a small helper on every page that returns the two stitching identifiers for the current visitor: ```js window.sourceloop.checkoutMetadata() // returns { sourceloop_anonymous_id: '...', sourceloop_id: '...' } ``` Pass that object through to Lemon Squeezy using the method that matches your checkout. Use this guide to pick: - **(1) Hosted Buy URLs** — The default for most stores. If your Buy / Subscribe button links to `https://your-store.lemonsqueezy.com/buy/...` or opens via the Lemon.js overlay, this is you. Covers digital products, license sales, indie SaaS, course launches, anything that hands the customer off to Lemon Squeezy's hosted checkout. - **(2) API-created Checkouts** — When you create the checkout server-side via the Lemon Squeezy API and redirect the customer to the returned URL. Use this for tamper-proof metadata (the client never sees it), pre-applied discounts, or per-customer custom prices. Wire each one you use. ### (1) Hosted Buy URLs Append the `checkout[custom][...]` query parameters to every Buy URL before the visitor opens it. The same pattern works for plain anchors and the Lemon.js overlay. Plain anchor tag: ```html [Subscribe](https://your-store.lemonsqueezy.com/buy/your-variant-id) ``` Lemon.js overlay: ```js const m = window.sourceloop.checkoutMetadata(); const url = new URL('https://your-store.lemonsqueezy.com/buy/your-variant-id'); url.searchParams.set('checkout[custom][sourceloop_anonymous_id]', m.sourceloop_anonymous_id); url.searchParams.set('checkout[custom][sourceloop_id]', m.sourceloop_id); LemonSqueezy.Url.Open(url.toString()); ``` ### (2) API-created Checkouts Pass the metadata in `checkout_data.custom` when creating the checkout server-side. This is tamper-proof because the client never sees it. Server (Node.js): ```js const checkout = await fetch('https://api.lemonsqueezy.com/v1/checkouts', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.LEMONSQUEEZY_API_KEY, 'Content-Type': 'application/vnd.api+json', Accept: 'application/vnd.api+json', }, body: JSON.stringify({ data: { type: 'checkouts', attributes: { checkout_data: { custom: meta }, }, relationships: { store: { data: { type: 'stores', id: STORE_ID } }, variant: { data: { type: 'variants', id: VARIANT_ID } }, }, }, }), }).then(r => r.json()); return { url: checkout.data.attributes.url }; ``` > **Let your AI assistant do the wiring** > If you'd rather not patch every Lemon Squeezy Buy URL by hand, paste the snippets above into Cursor, Claude Code, or your IDE assistant with the prompt "Apply these Sourceloop attribution patterns to every Lemon Squeezy Buy URL and API checkout in this codebase. Show me the files you plan to touch before editing." It'll find every `lemonsqueezy.com/buy/` link, every `LemonSqueezy.Url.Open` call, and every API checkout creation, and patch them in one pass. ## Step 5: Run a test order to verify Open the page with your Lemon Squeezy Buy button in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=ls-check` to the URL, and complete a real transaction. Lemon Squeezy test mode is the safest path if you'd rather not move real money, just enable test mode on the webhook while testing. Within seconds of Lemon Squeezy confirming the order, the customer should appear at the top of the **Contacts Hub** in SourceLoop with the test UTMs attached and a revenue event linked to the same contact. > **Not seeing the event?** > Inside SourceLoop, open the Lemon Squeezy connection card and check the **Last event** timestamp, that confirms the webhook is being received and verified. If it's stale, the issue is on Lemon Squeezy's webhook delivery side, check Settings -> Webhooks -> the endpoint's delivery log. If the event is being received but the contact isn't linking, the most common cause is the custom-data query parameters not being added to the Buy URL, double-check step 4. ## Where to see Lemon Squeezy revenue in SourceLoop ### Contacts Hub: revenue per contact Every Lemon Squeezy customer becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with a **revenue column** showing what they've paid you to date. Click a contact to expand the full revenue ledger, original order, every renewal, every upgrade, every refund, alongside the visitor's pre-purchase journey. Filter, sort, or segment the hub by revenue to surface your highest-value customers, or by source to see which channels are bringing in paying subscribers vs. just clicks. ![SourceLoop Contacts Hub showing a Lemon Squeezy customer with the revenue column, full pre-purchase journey, and revenue history](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard: revenue by channel [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) adds a **revenue column** alongside the breakdowns you already see: source, medium, campaign, landing page, content, term, device, country. So instead of "Twitter referral drove 600 sessions", you see "Twitter referral drove 600 sessions and $4,200 in attributed Lemon Squeezy revenue." Switch attribution models from the dropdown at the top to see how the numbers shift: - **Last Non-Direct** (the default, matches Google Analytics) - **First Touch** / **First Non-Direct** for top-of-funnel weighting - **Last Touch** for closing-channel weighting - **Linear** to split credit evenly across every touchpoint - **Position-Based (U-Shaped)** for 40% first / 40% last / 20% middle - **Time Decay** to weight recent touches higher (7-day half-life) A ProductHunt launch may look weak under Last Touch (because most subscribers come back through Google later) but dominate under First Touch. The model switcher is how you tell that story. ![SourceLoop attribution dashboard with Lemon Squeezy revenue column grouped by source, medium, and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) Once Lemon Squeezy revenue is flowing, push it back to **Google Ads, Meta, and LinkedIn as offline conversions with revenue values** so the bidding algorithms optimise toward real paying customers, not signups or vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with Lemon Squeezy hosted Buy URLs and API-created Checkouts? Yes. Both surfaces are covered. The webhook setup is identical for both, the only difference is in step 4, where you wire attribution. The article walks through each method. ### Lemon Squeezy is the merchant of record. Does that affect what SourceLoop sees? No. Merchant-of-record handling (VAT, sales tax, payouts) happens entirely on Lemon Squeezy's side. SourceLoop captures the order, subscription, and renewal events at face value, the gross amounts customers paid. ### Are subscription renewals attributed back to the original source? Yes. Every `subscription_payment_success` event gets stitched onto the same contact that started the subscription, so MRR and LTV stay tied to the channel that earned the original signup, not just the first order. ### How do refunds and chargebacks show up? Refunds (`subscription_payment_refunded`) flow into SourceLoop as negative revenue tied back to the original lead. The attribution dashboard nets them against the original source so reported channel revenue stays accurate. ### I'm using the Lemon.js overlay (`LemonSqueezy.Url.Open`). Anything different? No, treat it the same as the anchor-tag path. Patch the URL with the custom-data query parameters before calling `LemonSqueezy.Url.Open()`. The article shows both. ### Will my existing Lemon Squeezy automations (license keys, Discord roles, email receipts) still fire? Yes. SourceLoop subscribes to its own webhook endpoint. Every other destination you've already configured continues to receive events independently. --- # How to track lead source in Paddle Tie every Paddle transaction, subscription, and adjustment back to the channel that drove it. Revenue attribution for global SaaS, tax handled. Source: https://sourceloop.ai/help/track-lead-source-in-paddle/ Updated: 2026-05-28 --- Paddle is the merchant-of-record stack built for global SaaS, billing engine, tax compliance, subscription primitives, all consolidated. The piece it doesn't surface is **which marketing channel actually produced each subscriber**, which is the number you need to know before you decide where to spend. SourceLoop adds that layer. Every transaction, every subscription state change, every adjustment mapped back to the visitor's first session, so Paddle's "MRR by plan" report finally gets a companion: "MRR by channel". Five steps, around ten minutes. Works on every Paddle Billing account. ## Why this matters Paddle's whole value proposition is subscription lifecycle. The math on subscription lifecycle is impossible without attribution that follows the customer through every event: - A prospect arrives via a LinkedIn ad in February - They sign up on the Starter plan at $29/mo - They downgrade to Free in April after a layoff - They upgrade to Pro at $99/mo in October - They keep renewing for two more years Without revenue tracking, LinkedIn gets credited with a "trial started" event and never sees the $2,400+ of lifetime value that came out the other side. With Paddle wired into SourceLoop, every transaction, every plan change, every adjustment hangs off the same lead, and channel-level reporting reflects actual MRR and lifetime value, not just first-touch counts. ## What SourceLoop captures from Paddle Each Paddle customer arrives in SourceLoop tagged with: - **Acquisition channel** of the visitor at signup (paid, organic, social, referral, direct) - **UTM parameters** from the landing URL of the converting session - **Pages browsed** before the checkout was opened - **Time on site** and **return-visit count** ahead of the purchase - **Email + name** from Paddle's customer record - **First-touch landing page** plus the original referrer - **Source of the converting session** (often distinct from first-touch) - **Full subscription lifecycle**, transaction completed (initial + renewals), subscription created / activated (trial converted) / updated / canceled / paused, adjustments (refund, chargeback, credit) - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the site that hosts your Paddle.js checkout trigger or backend API - A **Paddle Billing account** with at least one Price live - **Admin access** to your Paddle Dashboard, you'll need it to add a notification destination ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup -> Tracking code** in the sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into the `` of every page on your site, especially the pages that open Paddle Checkout. From this point on, every visitor session, UTM, and journey is being recorded. The next steps connect that data to the transactions Paddle will start sending. ## Step 2: Create a notification destination in Paddle Paddle sends transaction and subscription events through "Notifications" (Paddle's term for webhooks). SourceLoop gives you a dedicated URL, you paste it into Paddle, and Paddle starts delivering events. 1. In SourceLoop, open **Setup -> Payment** in the sidebar. ![SourceLoop Setup Payment page showing the supported payment provider cards](/help/screenshots/sourceloop-payment-page.png) 2. Click the **Paddle** card. A drawer opens on the right with two tabs: **Connect webhook** and **Wire attribution**. Stay on the first tab and copy the **webhook URL** SourceLoop shows you. ![SourceLoop payment provider drawer with the webhook URL and signing secret fields](/help/screenshots/sourceloop-payment-webhook-drawer.webp) 3. Open the [Paddle Dashboard](https://vendors.paddle.com/), navigate to **Developer Tools -> Notifications**, and click **New destination**. 4. Paste SourceLoop's URL into the **Endpoint URL** field. 5. Under **Events**, select the events SourceLoop needs to read. The recommended set: `transaction.completed`, `subscription.created`, `subscription.activated`, `subscription.updated`, `subscription.canceled`, `subscription.paused`, `adjustment.created`. 6. Save the destination. Paddle generates an **endpoint Secret key** (prefixed `pdl_ntfset_...`), copy it. > **Use sandbox first if you have it** > Paddle has a separate sandbox environment. Add SourceLoop as a notification destination in your sandbox account first, run a few test transactions, then add it to your live account. Your sandbox connection in SourceLoop is independent of your live connection. ## Step 3: Paste the signing secret back into SourceLoop Paddle signs every notification with the endpoint secret key. 1. Copy the `pdl_ntfset_...` secret from the destination detail page in Paddle. 2. Back in the same SourceLoop drawer, paste it into the **Webhook secret key** field and click **Save**. SourceLoop's connection status flips from **pending** to **active** the first time Paddle delivers a verified event through. ## Step 4: Wire attribution into your checkout The webhook tells SourceLoop **a payment happened**. The attribution wiring tells SourceLoop **which visitor session** that payment belongs to. Without it, events still flow in, but stitching falls back to matching by customer email, lower fidelity, especially when a visitor pays without signing up first. Once the tracker is installed, it exposes a small helper on every page that returns the two stitching identifiers for the current visitor: ```js window.sourceloop.checkoutMetadata() // returns { sourceloop_anonymous_id: '...', sourceloop_id: '...' } ``` Pass that object through to Paddle using the method that matches your checkout. Use this guide to pick: - **(1) Paddle.js Checkout** — The default for most SaaS. If you open the Paddle checkout from the client by calling `Paddle.Checkout.open()` (overlay or inline), this is you. Covers most marketing-site pricing pages and in-app upgrade flows. - **(2) Transactions API** — When you create the Paddle Transaction server-side and hand the resulting transaction id to Paddle.js to open the checkout. Recommended for production where you want server-validated prices, pre-applied discounts, or tamper-proof metadata. Wire each one you use. ### (1) Paddle.js Checkout Pass the metadata into `customData` when opening the overlay. Paddle propagates it onto every transaction and subscription event that follows. ```js Paddle.Checkout.open({ items: [{ priceId: 'pri_xxx', quantity: 1 }], customer: { email: userEmail }, customData: window.sourceloop.checkoutMetadata(), }); ``` ### (2) Transactions API Create the Transaction server-side with `custom_data`, then hand the transaction id off to Paddle.js on the client. This is the recommended production pattern. Server (Node.js): ```js const txn = await fetch('https://api.paddle.com/transactions', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.PADDLE_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ items: [{ price_id: 'pri_xxx', quantity: 1 }], custom_data: meta, }), }).then(r => r.json()); return { txnId: txn.data.id }; ``` Client: ```js Paddle.Checkout.open({ transactionId: txnId }); ``` > **Let your AI assistant do the wiring** > If you'd rather not patch every Paddle integration point by hand, paste the snippets above into Cursor, Claude Code, or your IDE assistant with the prompt "Apply these Sourceloop attribution patterns to every Paddle integration in this codebase. Show me the files you plan to touch before editing." It'll find every `Paddle.Checkout.open` call and every `api.paddle.com/transactions` request, and patch them in one pass. ## Step 5: Run a test transaction to verify Open the page with your Paddle.Checkout trigger in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=paddle-check` to the URL, and complete a real transaction. Paddle sandbox is the safest path if you'd rather not move real money. Within seconds of Paddle confirming the transaction, the customer should appear at the top of the **Contacts Hub** in SourceLoop with the test UTMs attached and a revenue event linked to the same contact. > **Not seeing the event?** > Inside SourceLoop, open the Paddle connection card and check the **Last event** timestamp. If it's stale, the notification isn't reaching SourceLoop, check Developer Tools -> Notifications -> the destination's delivery log in Paddle. If events are received but the contact isn't linking, the most common cause is `customData` / `custom_data` not being passed at checkout, double-check step 4. ## Where to see Paddle revenue in SourceLoop ### Contacts Hub: revenue per contact Every Paddle customer becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with a **revenue column** showing what they've paid you to date. Click a contact to expand the full revenue ledger, initial transaction, every renewal, every plan change, every refund or chargeback, alongside the visitor's pre-purchase journey. Filter, sort, or segment the hub by revenue to surface your highest-value customers, or by source to see which channels are bringing in paying users vs. just clicks. ![SourceLoop Contacts Hub showing a Paddle customer with the revenue column, full pre-purchase journey, and revenue history](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard: revenue by channel [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) adds a **revenue column** alongside the breakdowns you already see: source, medium, campaign, landing page, content, term, device, country. So instead of "LinkedIn Ads drove 320 sessions", you see "LinkedIn Ads drove 320 sessions and $6,800 in attributed Paddle revenue." Switch attribution models from the dropdown at the top to see how the numbers shift: - **Last Non-Direct** (the default, matches Google Analytics) - **First Touch** / **First Non-Direct** for top-of-funnel weighting - **Last Touch** for closing-channel weighting - **Linear** to split credit evenly across every touchpoint - **Position-Based (U-Shaped)** for 40% first / 40% last / 20% middle - **Time Decay** to weight recent touches higher (7-day half-life) For long sales cycles (LinkedIn Ads → blog → demo → free trial → paid), Last Touch credits the demo while First Touch credits LinkedIn. Time Decay sits in between. Flip between models to see which one tells the truth. ![SourceLoop attribution dashboard with Paddle revenue column grouped by source, medium, and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) Once Paddle revenue is flowing, push it back to **Google Ads, Meta, and LinkedIn as offline conversions with revenue values** so the bidding algorithms optimise toward real paying customers, not free-trial signups. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with Paddle.js Checkout and the Transactions API? Yes. Both surfaces are covered. The webhook setup is identical for both, only the attribution wiring in step 4 differs. The article walks through each method. ### Paddle is a merchant of record. Does that affect the revenue numbers SourceLoop reports? No. Merchant-of-record handling (VAT, sales tax, payouts) sits entirely on Paddle's side. SourceLoop captures the gross transaction amount and the lifecycle events at face value. ### Are subscription renewals attributed back to the original source? Yes. Every `transaction.completed` event tied to a recurring billing cycle gets stitched onto the same contact that started the subscription, so MRR stays tied to the channel that earned the original signup. ### How do refunds, chargebacks, and credits show up? All three flow through Paddle's `adjustment.created` event with `action=refund | chargeback | credit`, and SourceLoop reports them as negative revenue tied back to the original lead. The attribution dashboard nets them against the original source. ### I use Paddle Billing's plan changes (upgrade, downgrade, pause, resume). Are those tracked? Yes. Every subscription state change generates a `subscription.updated` event with the new status, and SourceLoop attaches it to the existing contact so the timeline reflects the full subscription lifecycle. ### Will Paddle's existing notifications to Slack, my data warehouse, or my own backend still fire? Yes. SourceLoop subscribes as its own notification destination. Every other destination you've configured continues to receive Paddle events independently. --- # How to track lead source in Polar Tie every Polar order, subscription, and renewal back to the channel that drove it. Revenue attribution for open-source and developer tools. Source: https://sourceloop.ai/help/track-lead-source-in-polar/ Updated: 2026-05-28 --- Polar is the merchant-of-record platform built around the GitHub-native developer ecosystem, sponsorships, paid issues, license products, and SaaS, all from the same primitives. The blind spot, like every payments tool, is **which marketing channel earned each subscriber**. SourceLoop fills that in without changing how Polar charges your customers. Every order, every subscription event, every refund mapped back to the visitor's original session, so Polar's revenue dashboard finally answers the marketing question too, not just "what did we make" but "which content, repo, or sponsorship link produced it". Five steps, around ten minutes. Works on every Polar organisation. ## Why this matters Polar is heavy on indirect channels, GitHub README links, Discord drops, dev blog mentions, conference repos. The revenue lifecycle that comes out the other side is hard to map without attribution that follows the customer through: - A developer reads your README in March, clicks the Polar buy link, and starts the $19/mo plan - They upgrade to $99/mo in July when their team grows - They renew every month for two years - They cancel when they migrate to a self-hosted alternative Without revenue tracking, "github" gets credit for one event and never sees the $2,000+ that flowed from it. With Polar wired into SourceLoop, every state change, every renewal, every cancel hangs off the same contact, and channel-level MRR finally has signal. ## What SourceLoop captures from Polar Each Polar customer arrives in SourceLoop tagged with: - **Acquisition channel** of the visitor at signup (paid, organic, social, referral, direct, GitHub) - **UTM parameters** from the landing URL of the converting session - **Pages browsed** before checkout was opened - **Time on site** and **return-visit count** ahead of the purchase - **Email + name** from Polar's customer record - **First-touch landing page** plus the original referrer - **Source of the converting session** (often distinct from first-touch) - **Full revenue history**, orders (initial + renewals), subscription created / active (trial converted) / updated / canceled / revoked - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the site that hosts your Polar buy link or backend API - A **Polar organisation** with at least one product live - **Admin access** to your Polar Dashboard, you'll need it to add a webhook ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup -> Tracking code** in the sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into the `` of every page on your site, especially the pages with your Polar Checkout buttons or buy links. From this point on, every visitor session, UTM, and journey is being recorded. The next steps connect that data to the orders Polar will start sending. ## Step 2: Add a webhook in your Polar Dashboard Polar sends order and subscription events as webhooks. SourceLoop gives you a dedicated URL, you paste it into Polar, and Polar starts delivering events. 1. In SourceLoop, open **Setup -> Payment** in the sidebar. ![SourceLoop Setup Payment page showing the supported payment provider cards](/help/screenshots/sourceloop-payment-page.png) 2. Click the **Polar** card. A drawer opens on the right with two tabs: **Connect webhook** and **Wire attribution**. Stay on the first tab and copy the **webhook URL** SourceLoop shows you. ![SourceLoop payment provider drawer with the webhook URL and signing secret fields](/help/screenshots/sourceloop-payment-webhook-drawer.webp) 3. Open the [Polar Dashboard](https://polar.sh/), navigate to **Settings -> Webhooks**, and click **Add endpoint**. 4. Paste SourceLoop's URL into the endpoint URL field. 5. Under **Events**, select the events SourceLoop needs to read. The recommended set: `order.created`, `subscription.created`, `subscription.active`, `subscription.updated`, `subscription.canceled`, `subscription.revoked`. 6. Save the endpoint. Polar generates a **Signing secret** (prefixed `whsec_...`), copy it. > **Multiple endpoints are fine** > You can add SourceLoop alongside any webhook endpoint you've already configured. Polar delivers each event to every endpoint independently. ## Step 3: Paste the signing secret back into SourceLoop Polar signs every webhook with the secret it generated for the endpoint. 1. Copy the `whsec_...` value from the endpoint detail page in Polar. 2. Back in the same SourceLoop drawer, paste it into the **Signing secret** field and click **Save**. SourceLoop's connection status flips from **pending** to **active** the first time Polar delivers a verified event through. ## Step 4: Wire attribution into your checkout The webhook tells SourceLoop **a payment happened**. The attribution wiring tells SourceLoop **which visitor session** that payment belongs to. Without it, events still flow in, but stitching falls back to matching by customer email, lower fidelity, especially for one-off buys where the customer has no pre-purchase site history. Once the tracker is installed, it exposes a small helper on every page that returns the two stitching identifiers for the current visitor: ```js window.sourceloop.checkoutMetadata() // returns { sourceloop_anonymous_id: '...', sourceloop_id: '...' } ``` Pass that object through to Polar using the method that matches your checkout. Use this guide to pick: - **(1) Checkouts API** — Recommended for any production setup. If you create the checkout server-side using the Polar SDK (or the underlying REST API) and redirect the customer to the returned URL, this is you. Picked by SaaS, paid licenses, and anyone who needs server-validated prices or pre-applied discounts. - **(2) Polar Checkout Links** — Static `buy.polar.sh/...` URLs you paste into a GitHub README, Discord, a docs page, or a blog post. Easiest to deploy, lower fidelity than the API because the metadata travels through query parameters. Wire each one you use. ### (1) Checkouts API Pass `metadata` when creating the checkout server-side. Polar attaches it to the resulting Order and Subscription so every downstream event stays stitched. Server (Node.js): ```js import { Polar } from '@polar-sh/sdk'; const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN }); const checkout = await polar.checkouts.create({ products: ['prod_xxx'], successUrl: 'https://your.site/thanks', metadata: meta, }); return { url: checkout.url }; ``` ### (2) Polar Checkout Links Patch each Polar buy link with `metadata[key]` query parameters before the visitor clicks. ```html [Subscribe](https://buy.polar.sh/polar_cl_xxx) ``` > **Customer-supplied metadata must be enabled on the link** > Confirm in your Polar Dashboard that customer-supplied metadata is enabled for the checkout link. If it's disabled, the query parameters are dropped, and SourceLoop falls back to matching by customer email. > **Let your AI assistant do the wiring** > If you'd rather not patch every Polar integration point by hand, paste the snippets above into Cursor, Claude Code, or your IDE assistant with the prompt "Apply these Sourceloop attribution patterns to every Polar integration in this codebase. Show me the files you plan to touch before editing." It'll find every `polar.checkouts.create` call and every `buy.polar.sh` link, and patch them in one pass. ## Step 5: Run a test order to verify Open the page with your Polar checkout in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=polar-check` to the URL, and complete a real transaction. Polar's test mode is the safest path if you'd rather not move real money. Within seconds of Polar confirming the order, the customer should appear at the top of the **Contacts Hub** in SourceLoop with the test UTMs attached and a revenue event linked to the same contact. > **Not seeing the event?** > Inside SourceLoop, open the Polar connection card and check the **Last event** timestamp. If it's stale, the webhook isn't reaching SourceLoop, check Settings -> Webhooks -> the endpoint's delivery log in Polar. If events are received but the contact isn't linking, the most common cause is `metadata` not being passed at checkout, or customer-supplied metadata being disabled on the link, double-check step 4. ## Where to see Polar revenue in SourceLoop ### Contacts Hub: revenue per contact Every Polar customer becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with a **revenue column** showing what they've paid you to date. Click a contact to expand the full revenue ledger, initial order, every renewal, every plan change, every cancellation, alongside the visitor's pre-purchase journey. Filter, sort, or segment the hub by revenue to surface your highest-value customers, or by source to see which channels (GitHub, Reddit, your docs site) are bringing in paying subscribers vs. just stargazers. ![SourceLoop Contacts Hub showing a Polar customer with the revenue column, full pre-purchase journey, and revenue history](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard: revenue by channel [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) adds a **revenue column** alongside the breakdowns you already see: source, medium, campaign, landing page, content, term, device, country. So instead of "github.com referral drove 1,200 sessions", you see "github.com referral drove 1,200 sessions and $3,400 in attributed Polar revenue." Switch attribution models from the dropdown at the top to see how the numbers shift: - **Last Non-Direct** (the default, matches Google Analytics) - **First Touch** / **First Non-Direct** for top-of-funnel weighting - **Last Touch** for closing-channel weighting - **Linear** to split credit evenly across every touchpoint - **Position-Based (U-Shaped)** for 40% first / 40% last / 20% middle - **Time Decay** to weight recent touches higher (7-day half-life) For developer products, the README typically gets a discovery role and the docs get the conversion. First Touch shows you the README is doing its job; Last Touch shows you the docs page is closing. Both numbers are real, depending on which question you're asking. ![SourceLoop attribution dashboard with Polar revenue column grouped by source, medium, and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) Once Polar revenue is flowing, push it back to **Google Ads, Meta, and LinkedIn as offline conversions with revenue values** so the bidding algorithms optimise toward paying customers, not just clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with the Polar Checkouts API and Polar's static Checkout Links? Yes. Both surfaces are covered. The webhook setup is identical for both, only the attribution wiring in step 4 differs. The article walks through each method. ### Are subscription renewals attributed back to the original source? Yes. Every `order.created` event tied to a recurring subscription cycle gets stitched onto the same contact that started the subscription, so MRR stays tied to the channel that earned the original signup. ### How do refunds and cancellations show up? Cancellations come through as `subscription.canceled` and `subscription.revoked`, which SourceLoop records as a state change on the contact. Refunds reduce the contact's attributed revenue and flow back to the original source in the dashboard. ### I'm using Polar as a GitHub-integrated sponsorship / paid issues product. Anything different? No. Whether Polar is hosting sponsorships, paid issues, products, or full subscriptions, every order or subscription event flows through the same webhook channel. SourceLoop attributes them the same way. ### My Polar Checkout Link is shared on GitHub README and Discord, not on a tracked page. Are those purchases attributed? When the customer clicks a Checkout Link from outside your tracked site, SourceLoop can't see a pre-purchase journey. The purchase is still captured (via the webhook) but the source shows as "Direct" since there's no UTM trail. Patch the Checkout Link with metadata via JavaScript on a tracked page to keep the source intact. ### Will my Polar webhooks to other tools (Slack, my backend) still fire? Yes. SourceLoop subscribes as its own endpoint. Every other endpoint you've configured continues to receive Polar events independently. --- # How to track lead source in Dodo Payments Tie every Dodo Payments transaction and recurring billing event back to the channel that drove it. Revenue attribution for SaaS and digital. Source: https://sourceloop.ai/help/track-lead-source-in-dodo-payments/ Updated: 2026-05-28 --- Dodo Payments is the merchant-of-record payments platform built for global SaaS and digital products, especially strong for sellers based outside the US who want a checkout that handles cross-border compliance and tax. The reporting gap, like every payments tool, is **which marketing channel earned each customer**. SourceLoop fills that in. Every payment, every recurring renewal, every refund mapped back to the visitor's original session, so Dodo's transaction list finally has a "by source" view, not just "by amount" or "by date". Five steps, around ten minutes. Works on every Dodo Payments account. ## Why this matters Dodo handles both one-shot payments and recurring subscriptions. For one-shot purchases (an ebook, a course, a yearly license), first-touch revenue attribution is the whole story. For subscriptions, it's the start of the story: - A founder discovers your tool via a YouTube video in May - They buy the $39/mo Starter plan - They upgrade to Pro ($129/mo) in August when their team scales - They keep renewing for the next 18 months Without revenue tracking, YouTube gets credit for one "payment succeeded" event and never sees the $2,400+ in lifetime revenue. With Dodo wired into SourceLoop, every renewal, every plan change, every refund hangs off the same lead, and channel-level reporting reflects real MRR, not just first-payment counts. ## What SourceLoop captures from Dodo Payments Each Dodo customer arrives in SourceLoop tagged with: - **Acquisition channel** of the visitor at signup (paid, organic, social, referral, direct) - **UTM parameters** from the landing URL of the converting session - **Pages browsed** before checkout was opened - **Time on site** and **return-visit count** ahead of the purchase - **Email + name** from Dodo's customer record - **First-touch landing page** plus the original referrer - **Source of the converting session** (often distinct from first-touch) - **Full revenue history**, payment succeeded (initial + renewals via `subscription.renewed`), subscription active (trial converted), plan changed, on hold, canceled, expired, failed, refunds - **Device, country, browser** ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Edit access** to the site that hosts your Dodo Payments checkout trigger or backend API - A **Dodo Payments account** with at least one product live - **Developer access** to your Dodo Dashboard, you'll need it to add a webhook ## Step 1: Install the SourceLoop tracking script Sign in to SourceLoop, open **Setup -> Tracking code** in the sidebar, and copy the snippet. ![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png) Paste it into the `` of every page on your site, especially the pages that lead into a Dodo checkout. From this point on, every visitor session, UTM, and journey is being recorded. The next steps connect that data to the payments Dodo will start sending. ## Step 2: Add a webhook in your Dodo Dashboard Dodo sends payment and subscription events as webhooks. SourceLoop gives you a dedicated URL, you paste it into Dodo, and Dodo starts delivering events. 1. In SourceLoop, open **Setup -> Payment** in the sidebar. ![SourceLoop Setup Payment page showing the supported payment provider cards](/help/screenshots/sourceloop-payment-page.png) 2. Click the **Dodo Payments** card. A drawer opens on the right with two tabs: **Connect webhook** and **Wire attribution**. Stay on the first tab and copy the **webhook URL** SourceLoop shows you. ![SourceLoop payment provider drawer with the webhook URL and signing secret fields](/help/screenshots/sourceloop-payment-webhook-drawer.webp) 3. Open the [Dodo Dashboard](https://app.dodopayments.com/), navigate to **Developer -> Webhooks**, and click **Add endpoint**. 4. Paste SourceLoop's URL into the endpoint URL field. 5. Under **Events**, enable the subscription, payment, and refund events. The recommended set: `payment.succeeded`, `refund.succeeded`, `subscription.active`, `subscription.renewed`, `subscription.plan_changed`, `subscription.on_hold`, `subscription.cancelled`, `subscription.expired`, `subscription.failed`. 6. Save the endpoint. Dodo generates a **signing secret**, copy it. > **Run in test mode first** > Dodo has a test environment you can flip the dashboard into. Add SourceLoop as a webhook in test mode first, run a couple of test transactions, then add it to your live mode. The two environments stay separate in SourceLoop. ## Step 3: Paste the signing secret back into SourceLoop Dodo signs every webhook with the secret it generated for the endpoint. 1. Copy the signing secret from the endpoint detail page in Dodo. 2. Back in the same SourceLoop drawer, paste it into the **Signing secret** field and click **Save**. SourceLoop's connection status flips from **pending** to **active** the first time Dodo delivers a verified event through. ## Step 4: Wire attribution into your checkout The webhook tells SourceLoop **a payment happened**. The attribution wiring tells SourceLoop **which visitor session** that payment belongs to. Without it, events still flow in, but stitching falls back to matching by customer email, lower fidelity, especially when a visitor pays without signing up first. Once the tracker is installed, it exposes a small helper on every page that returns the two stitching identifiers for the current visitor: ```js window.sourceloop.checkoutMetadata() // returns { sourceloop_anonymous_id: '...', sourceloop_id: '...' } ``` Pass that object through to Dodo using the API that matches your billing model. Use this guide to pick: - **(1) Payments API** — Use this for one-time purchases. Single-shot sales, lifetime deals, course purchases, license unlocks, anything where the customer pays once. - **(2) Subscriptions API** — Use this for recurring billing. Monthly or annual SaaS plans, memberships, subscription-based access. Metadata persists on the subscription object so every renewal event stays stitched to the original lead. Wire each one you use, both share the same `metadata` field name. ### (1) Payments API Create the payment server-side with `metadata`. Dodo attaches it to the payment object and propagates it onto refund events. Server (Node.js): ```js const payment = await fetch('https://api.dodopayments.com/payments', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.DODO_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ product_cart: [{ product_id: 'prod_xxx', quantity: 1 }], customer: { email: userEmail, name: userName }, payment_link: true, metadata: meta, }), }).then(r => r.json()); return { url: payment.payment_link }; ``` ### (2) Subscriptions API Same pattern, different endpoint. Metadata persists on the subscription object so every renewal event stays stitched. Server (Node.js): ```js const sub = await fetch('https://api.dodopayments.com/subscriptions', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.DODO_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ product_id: 'prod_xxx', customer: { email: userEmail, name: userName }, payment_link: true, metadata: meta, }), }).then(r => r.json()); return { url: sub.payment_link }; ``` > **Dodo's static Share Links can't carry per-visitor metadata** > The "Share link" button in the Dodo Dashboard generates a fixed URL that doesn't accept query-string metadata. For attribution you need to create payments through the API (above) and pass `metadata`, or rely on email matching as a fallback. > **Let your AI assistant do the wiring** > If you'd rather not patch every Dodo integration point by hand, paste the snippets above into Cursor, Claude Code, or your IDE assistant with the prompt "Apply these Sourceloop attribution patterns to every Dodo Payments integration in this codebase. Show me the files you plan to touch before editing." It'll find every `api.dodopayments.com/payments` and `api.dodopayments.com/subscriptions` call, and patch them in one pass. ## Step 5: Run a test payment to verify Open the page with your Dodo checkout in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=dodo-check` to the URL, and complete a real transaction. Dodo test mode is the safest path if you'd rather not move real money, just toggle to test mode in the dashboard while testing. Within seconds of Dodo confirming the payment, the customer should appear at the top of the **Contacts Hub** in SourceLoop with the test UTMs attached and a revenue event linked to the same contact. > **Not seeing the event?** > Inside SourceLoop, open the Dodo connection card and check the **Last event** timestamp. If it's stale, the webhook isn't reaching SourceLoop, check Developer -> Webhooks -> the endpoint's delivery log in Dodo. If events are received but the contact isn't linking, the most common cause is `metadata` not being passed in the API call (or the customer used a static Share Link), double-check step 4. ## Where to see Dodo revenue in SourceLoop ### Contacts Hub: revenue per contact Every Dodo customer becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with a **revenue column** showing what they've paid you to date. Click a contact to expand the full revenue ledger, initial payment, every renewal, every plan change, every refund, alongside the visitor's pre-purchase journey. Filter, sort, or segment the hub by revenue to surface your highest-value customers, or by source to see which channels are bringing in paying users vs. just sign-ups. ![SourceLoop Contacts Hub showing a Dodo customer with the revenue column, full pre-purchase journey, and revenue history](/help/screenshots/sourceloop-lead-journey-demo.webp) ### Attribution dashboard: revenue by channel [app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) adds a **revenue column** alongside the breakdowns you already see: source, medium, campaign, landing page, content, term, device, country. So instead of "YouTube referral drove 400 sessions", you see "YouTube referral drove 400 sessions and $5,100 in attributed Dodo revenue." Switch attribution models from the dropdown at the top to see how the numbers shift: - **Last Non-Direct** (the default, matches Google Analytics) - **First Touch** / **First Non-Direct** for top-of-funnel weighting - **Last Touch** for closing-channel weighting - **Linear** to split credit evenly across every touchpoint - **Position-Based (U-Shaped)** for 40% first / 40% last / 20% middle - **Time Decay** to weight recent touches higher (7-day half-life) A YouTube video that introduces your product but doesn't close the sale looks weak under Last Touch and dominant under First Touch. Picking the right model depends on whether you're measuring discovery or conversion, the switcher lets you flip between them in one click. ![SourceLoop attribution dashboard with Dodo revenue column grouped by source, medium, and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp) Once Dodo revenue is flowing, push it back to **Google Ads, Meta, and LinkedIn as offline conversions with revenue values** so the bidding algorithms optimise toward real paying customers, not free signups or vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring. ## Frequently Asked Questions ### Does this work with both the Dodo Payments API and the Subscriptions API? Yes. Both surfaces are covered, and they share the same webhook setup. Only the attribution wiring in step 4 differs, and the article walks through each. ### Are subscription renewals attributed back to the original source? Yes. Every `subscription.renewed` event gets stitched onto the same contact that started the subscription, so MRR and LTV stay tied to the channel that earned the original signup, not just the first payment. ### How do refunds and chargebacks show up? Refunds (`refund.succeeded`) and failed states (`subscription.failed`, `subscription.expired`) flow into SourceLoop and reduce the contact's attributed revenue. The attribution dashboard nets them against the original source. ### Dodo has a "Share link" button in the dashboard that produces a static payment link. Are clicks on those links attributed? Not directly. Static payment links don't carry per-visitor metadata, so SourceLoop falls back to email matching for those purchases. For accurate per-visitor stitching, create payments through the Dodo API and pass the metadata, the article shows how. ### I sell globally through Dodo. Do exchange rates and local currencies cause issues? No. Dodo reports gross transaction values in the original currency through the webhook, and SourceLoop captures the same amount. The attribution dashboard's revenue column shows your store's reporting currency. ### Will my Dodo webhooks to other tools (Slack, CRM, my own backend) still fire? Yes. SourceLoop subscribes as an additional endpoint. Every other destination you've configured continues to receive Dodo events independently. --- # What Shopify data SourceLoop stores and deletes What SourceLoop accesses from your Shopify store, where it is stored, what is deleted on uninstall, and how data-deletion requests are handled. Source: https://sourceloop.ai/help/shopify-data-deletion/ Updated: 2026-07-03 --- This article is the complete, plain-language record of what SourceLoop does with data from your Shopify store: what it reads, where it is kept, what happens when you uninstall, and how customer and shop deletion requests are handled. It exists so that you (and your data-protection officer, if you have one) have a clear answer to every reasonable privacy question, and so the integration meets Shopify's requirements for how apps handle protected customer data. It is the companion to [How to set up marketing attribution tracking for Shopify](/help/marketing-attribution-for-shopify/). ## What SourceLoop accesses from Shopify When you install the SourceLoop app and approve it, you grant **read-only** access to three areas of your store: - **Orders**, to match each sale to the marketing source that drove it - **Customers**, to roll repeat purchases up to the same person for LTV - **Checkouts**, to understand completed and abandoned checkout activity SourceLoop **never requests write access**. It cannot change products, prices, inventory, orders, fulfillment, or customer records in your Shopify admin. Its role is strictly to read, so it can attribute. From those areas, the data actually stored in your SourceLoop workspace is: - Order details: order value, line items, discounts, currency, status, and timestamps - Customer identity: email and name from the Shopify customer record - The marketing journey stitched to each order: source, medium, campaign, UTMs, ad click IDs, landing page, referrer, and pages browsed - Refund and cancellation events, netted against the original order - Device, country, and browser of the converting session ## Where it is stored and who can see it - Imported Shopify data lives **only inside your own private SourceLoop workspace**. - It is **not shared** with other SourceLoop customers, **not sold**, and **not used** for anything beyond producing your attribution reports and dashboards. - Sensitive values, including your store's access token, are **encrypted at rest**. - Only members you have invited to your SourceLoop workspace can view it. ## What happens when you uninstall You can remove SourceLoop at any time from **Shopify Settings -> Apps and sales channels -> SourceLoop -> Uninstall**. The moment you uninstall: - **Access ends immediately.** Shopify revokes the app's access token, and SourceLoop deletes its copy of that token. No further calls are made to your store. - **Storefront tracking stops.** SourceLoop's app embed is disabled automatically when the app is uninstalled. Your theme is untouched because the embed was a toggle, not code, and nothing was ever pasted into it. - **Your Shopify data is left exactly as it was.** SourceLoop has read-only access and never had the ability to delete or change anything in your store. Uninstalling also **begins the deletion of the data SourceLoop imported**, described next. ## How deletion requests are handled Shopify defines three standard privacy requests that every app must honor. SourceLoop handles all three automatically. You do not need to configure anything. ### 1. Shop data erasure (after you uninstall) **48 hours after you uninstall** the app, Shopify notifies SourceLoop to erase your store's data. On receiving that notice, SourceLoop **permanently deletes** everything it imported from that store: - All imported orders, customers, and checkouts - The marketing journeys and attribution records tied to them - The store connection record and any remaining tokens - Operational logs associated with the connection The 48-hour gap is Shopify's standard window; it exists so an accidental uninstall can be reversed by reinstalling. If you want your data removed **sooner**, email **hello@sourceloop.ai** and we process the erasure right away. ### 2. Customer data erasure (a shopper asks to be deleted) When one of your shoppers asks you to delete their personal data, Shopify forwards that request to every installed app, including SourceLoop. On receiving it, SourceLoop **erases that individual's records** for your store: their contact, their orders, and their journey. This happens automatically and needs nothing from you. Deletion on our side only removes the copy stored in your SourceLoop workspace. The original customer record in your Shopify admin is governed by Shopify and by your own store policy, not by SourceLoop. ### 3. Customer data access (a shopper asks for their data) When a shopper requests a copy of the personal data held on them, Shopify forwards that request to SourceLoop as well. We **compile the records** associated with that customer for your store and make them available to you, the merchant, so you can provide them to the shopper. ## Data retention at a glance | Data | When it is deleted | | --- | --- | | Store access token | Immediately on uninstall | | Storefront tracking (app embed) | Disabled automatically on uninstall | | Imported orders, customers, checkouts, journeys | Erased 48 hours after uninstall (or on request) | | A specific shopper's records | Erased when that customer's deletion request is received | | Connection record and logs | Purged as part of the shop erasure | Nothing imported from Shopify is retained after the erasure completes. There is no long-term archive. ## Requesting deletion or a written record manually Most deletion happens automatically through the flows above. For anything beyond that: 1. Email **hello@sourceloop.ai** with the subject "Shopify data deletion request" and your store's `.myshopify.com` domain. 2. Confirm your identity (we use the authenticated SourceLoop workspace owner email). 3. We acknowledge within 2 business days and complete the erasure within 30 days, confirming in writing. For a data-protection review, we can also provide a **data-processing agreement (DPA)** and a written summary of exactly what was accessed, retained, and deleted for your specific store. > **Not legal advice** > This guide is general information about how SourceLoop handles Shopify data, not legal advice. Your obligations as a merchant depend on your jurisdiction and how you use the data. If you are unsure, check with a privacy professional for your specific situation. ## Frequently Asked Questions ### What happens the moment I uninstall SourceLoop from Shopify? Access ends immediately. The store's access token is revoked and deleted on SourceLoop's side, and all further calls to your Shopify store stop. The SourceLoop app embed is disabled automatically when the app is uninstalled, so storefront tracking stops too. Nothing in your Shopify admin is changed or deleted by us. ### Does uninstalling delete the order and customer data SourceLoop already imported? Uninstalling starts the deletion process. Shopify notifies SourceLoop to erase your store's data 48 hours after uninstall, and SourceLoop then permanently deletes the imported orders, customers, and checkouts for that store. If you need it removed sooner, email hello@sourceloop.ai and we process it right away. ### How does a customer's "delete my data" request work? When a shopper asks a merchant to delete their data, Shopify forwards that request to every installed app, including SourceLoop. On receiving it, SourceLoop erases that individual's records (their contact, orders, and journey) tied to your store. This is automatic and requires nothing from you. ### Can a customer request a copy of the data you hold on them? Yes. Shopify forwards data-access requests to SourceLoop the same way. We compile the records associated with that customer for your store and make them available to you as the merchant, so you can pass them to the shopper. ### What does SourceLoop never touch in my Shopify store? SourceLoop holds read-only access to orders, customers, and checkouts. It cannot and does not modify products, prices, inventory, orders, fulfillment, or any customer records inside Shopify. Deletion on our side only affects the copies stored in your SourceLoop workspace, never the originals in Shopify. ### Where is the imported Shopify data stored? Only in your own private SourceLoop workspace. It is not shared with other customers, not sold, and not used for any purpose beyond producing your attribution reports. Sensitive values such as access tokens are encrypted at rest. ### How do I get a written record for a data-protection review? Email hello@sourceloop.ai. We can provide a data-processing agreement (DPA) and a written summary of exactly what was accessed, retained, and deleted for your specific store, suitable for a GDPR or SOC 2 review. --- # Section: CRM Syncing Push lead source, UTMs, lifecycle stage, and the full visitor journey into your CRM. Native sync for HubSpot, Salesforce, and Pipedrive. # How to track lead source in HubSpot Sync lead source, UTMs, and the full marketing journey from SourceLoop into HubSpot contact, company, and deal records. OAuth-based, 5-minute setup. Source: https://sourceloop.ai/help/connect-hubspot-to-sourceloop/ Updated: 2026-05-28 --- Connecting HubSpot to SourceLoop is a one-time OAuth setup that takes about five minutes. Once connected, every visitor's source, journey, and conversion (forms, meetings, chats, payments) gets pushed into the matching HubSpot contact, with their UTM history, landing page, and the full marketing journey attached. This guide covers the connect flow only. For what to do next, see: - [Push UTMs and lead source to HubSpot contact properties](/help/push-utms-to-hubspot/) - [Map SourceLoop stages to HubSpot lifecycle stages](/help/map-hubspot-lifecycle-stages/) ## Why connect HubSpot to SourceLoop? HubSpot is a brilliant CRM, but its native attribution (Marketing Hub's Source Type field) only goes deep at the Professional and Enterprise tiers, and even then it's a single source per contact. SourceLoop adds: - **First-touch and last-touch** source on every contact, not just one - **Full UTM history** (source, medium, campaign, content, term) per touchpoint - **Multi-touch attribution models** (first, last, linear, position-based, time-decay) - **Pre-conversion journey** (every page the visitor browsed, every session they had) - **Revenue-by-channel** when you wire payments in (Stripe, Polar, etc.) All of this sits on the **HubSpot contact, company, and deal records** alongside everything you already track. Your reps see source data inside the contact view; your dashboards group revenue by channel; your nurture flows can trigger on UTM values. ## Before you start You'll need: - A **SourceLoop workspace** with the [tracking pixel installed](/help/install-the-tracking-pixel/) - A **HubSpot account** (any tier, including Free) - HubSpot **Super Admin** or **Account Access** permission for the user authorising the connection - **Admin** or **Owner** role in SourceLoop (Editors can't add integrations) ## Step 1: Open the CRM integrations page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Setup** in the left sidebar. ![SourceLoop Home page with the Setup entry in the left sidebar highlighted](/help/screenshots/sourceloop-setup-navigation.webp) 3. Click the **CRM** tab inside Setup. ![SourceLoop Setup CRM page showing the supported CRM provider cards including HubSpot, Salesforce, and Pipedrive](/help/screenshots/sourceloop-crm-integration-page.webp) 4. Scroll to the **HubSpot** card and click **Connect**. ![SourceLoop CRM drawer with the HubSpot Connect button highlighted](/help/screenshots/sourceloop-connect-hubspot.webp) You'll be redirected to HubSpot's OAuth consent screen. ## Step 2: Authorise SourceLoop on HubSpot's consent screen 1. Sign in to HubSpot if you aren't already. 2. Select the HubSpot account you want to connect (if you have multiple). 3. Review the scopes SourceLoop is requesting (read/write on contacts, companies, deals, owners, lists, schemas, plus webhook subscriptions for chat events). 4. Click **Connect app**. HubSpot redirects you back to SourceLoop, the connection card now shows **Connected** with the date and your HubSpot portal ID. > **HubSpot tokens auto-refresh** > HubSpot OAuth access tokens are 30-minute-lived, but SourceLoop stores the refresh token and rotates the access token automatically. You won't need to reconnect unless you revoke the app from HubSpot's Connected Apps page. ## Step 3: Pick what to sync After connecting, the HubSpot drawer opens with sync settings: 1. **Inbound sync** (HubSpot → SourceLoop): pulls your existing HubSpot contacts, companies, and deals into SourceLoop so SourceLoop can stitch attribution onto them. 2. **Outbound sync** (SourceLoop → HubSpot): pushes new leads, UTM values, and journey data from SourceLoop into HubSpot contact properties. Most teams enable **both**. Inbound brings your existing pipeline into SourceLoop so dashboards show full revenue. Outbound puts the source data into HubSpot so your reps and workflows can act on it. Click **Save** to start the first sync. ## Step 4: Wait for the initial sync The first sync runs immediately and pulls existing HubSpot records into SourceLoop. Depending on the size of your portal, this takes anywhere from 30 seconds (small accounts) to ~30 minutes (50k+ contacts). You don't need to keep the SourceLoop tab open — the sync runs server-side. Watch the **Last sync** timestamp on the HubSpot card. When it ticks over to a recent time, the initial sync is done. From here, deltas run every 15 minutes automatically. ## What happens next - **Forward** SourceLoop captures every new visitor session, UTM, conversion, and revenue event, and pushes the source data into HubSpot contact properties on the next sync cycle. - **Field mapping** — by default SourceLoop maps to a fixed set of contact properties (`sourceloop_first_source`, `sourceloop_last_source`, `sourceloop_landing_page`, plus the UTM fields). To map to your own custom properties or change which fields write where, see [Push UTMs and lead source to HubSpot contact properties](/help/push-utms-to-hubspot/). - **Lifecycle stage / lead status** — SourceLoop's internal lifecycle stages map onto HubSpot's `lifecyclestage` and `hs_lead_status` properties. To customise the mapping, see [Map SourceLoop stages to HubSpot lifecycle stages](/help/map-hubspot-lifecycle-stages/). - **Troubleshooting** — if a sync looks stuck or a contact isn't updating, see [Troubleshoot HubSpot sync issues](/help/troubleshoot-hubspot-sync/). ## What gets written to HubSpot Out of the box, the following contact properties are populated for every contact synced: - **First-touch source / medium / campaign / content / term** — the visitor's original UTMs - **Last-touch source / medium / campaign / content / term** — the UTMs of the converting session - **First-touch landing page** — the page they first arrived on - **Last-touch landing page** — the page they were on when they converted - **Sourceloop ID** — internal identifier that links the HubSpot contact to the SourceLoop record (don't delete or edit this) The Field Mapping tab lets you remap any of these to your own HubSpot properties. ## Frequently Asked Questions ### Do I need HubSpot Marketing Hub Professional or Enterprise? No. The HubSpot OAuth integration works on every HubSpot tier including Free and Starter, as long as your account has access to the contact / company / deal objects (which all tiers do). The only features that require Marketing Hub Pro+ are HubSpot's own UTM tracking, but you don't need those if you're using SourceLoop. ### Does this sync historic leads or only new ones going forward? Both. The initial sync pulls in your existing HubSpot contacts (matched by email) and stamps SourceLoop attribution onto any future page-visit / form-fill / meeting / chat / payment event from those people. Forward-going contacts and conversions sync on the 15-minute cadence. ### Will SourceLoop overwrite HubSpot fields I already have populated? No, not by default. SourceLoop writes to its own set of contact properties (the UTM and journey fields) and leaves all other fields untouched. If a field collision happens (e.g., you mapped SourceLoop's `lead_source` to HubSpot's standard "Original source"), the most recent value wins. You control the mapping in the Field Mapping tab. ### How often does SourceLoop sync with HubSpot? Every 15 minutes for delta sync (only contacts modified since the last run). The initial sync after connecting can take longer depending on the size of your HubSpot account, anywhere from a few seconds to ~30 minutes for very large databases. ### Can I sync more than one HubSpot account into the same SourceLoop workspace? One HubSpot account per SourceLoop website. If you manage multiple HubSpot portals (e.g., one per brand), create a separate website in SourceLoop for each and connect them independently. ### Is SourceLoop a HubSpot Marketplace app? SourceLoop uses HubSpot's standard OAuth flow (the same one Marketplace apps use). The connection is authorised through HubSpot's official screen and can be revoked any time from HubSpot's Connected Apps settings. --- # How to push UTM parameters to HubSpot Map SourceLoop's UTM and source fields to HubSpot contact, company, and deal properties. Default mapping, custom properties, inbound vs outbound direction. Source: https://sourceloop.ai/help/push-utms-to-hubspot/ Updated: 2026-05-28 --- By default, SourceLoop ships with a sensible mapping that writes UTM and source data to a set of `sourceloop_*` custom properties on the HubSpot contact. For most teams that's enough. For teams that already have their own UTM properties (or want SourceLoop's data to drive a HubSpot workflow that already exists), you can remap any field to any HubSpot property. This article covers the field mapping system end to end, default mapping, customising it, contact vs company vs deal mappings, and direction. ## Before you start You'll need: - [HubSpot connected to SourceLoop](/help/connect-hubspot-to-sourceloop/) (the connection must show **Connected**) - HubSpot **Super Admin** or **Account Access** (to create custom properties on HubSpot's side if you want SourceLoop to auto-create them) - **Admin** or **Owner** role in SourceLoop ## The fields SourceLoop can push SourceLoop tracks every visitor session across these dimensions. Any of them can be pushed to HubSpot: **Contact identity:** - Email, contact name, phone, company name, country, city, title, LinkedIn URL **Attribution (per-touchpoint):** - First-touch: source, medium, campaign, content, term, landing page, channel, keyword - Last-touch (converting session): same set - Multi-touch model output: configurable per workspace **Lifecycle:** - Lead status (raw and mapped, see [Map SourceLoop stages to HubSpot lifecycle stages](/help/map-hubspot-lifecycle-stages/)) - Lifecycle stage (raw and mapped) - Lead score - Qualified (true/false) **Revenue:** - Quote value (expected revenue from this lead) - Sales value (realised revenue, when payment integrations are connected) ## The default mapping When you connect HubSpot and enable outbound sync, SourceLoop ships these default mappings on the **contact** entity: - `first_channel` → `sourceloop_first_channel` - `first_source` → `sourceloop_first_source` - `first_medium` → `sourceloop_first_medium` - `first_campaign` → `sourceloop_first_campaign` - `first_landing_page` → `sourceloop_first_landing_page` - `latest_channel` → `sourceloop_latest_channel` - `latest_source` → `sourceloop_latest_source` - `latest_medium` → `sourceloop_latest_medium` - `latest_campaign` → `sourceloop_latest_campaign` - `latest_landing_page` → `sourceloop_latest_landing_page` - `lead_status_raw` → `hs_lead_status` - `lifecycle_stage_raw` → `lifecyclestage` These custom properties are created in your HubSpot portal automatically the first time SourceLoop writes to them, you don't have to pre-create anything. ## Step 1: Open the HubSpot drawer 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> CRM -> HubSpot**. The drawer opens with the connection status at the top and several stacked sections: **Lead status mapping**, **Lifecycle stage mapping**, **Deal stages**, and **Field mappings**. ![SourceLoop HubSpot drawer showing sync toggles, lead status mapping, lifecycle stage mapping, deal stages, and field mappings sections](/help/screenshots/sourceloop-hubspot-field-mapping.webp) 3. Scroll down to the **Field mappings** section. It lists the entities you can map (Contacts, Companies, Deals) each with its own **Edit** button. 4. Click **Edit** next to **Contacts** (or **Companies** / **Deals** depending on which entity you're mapping). ## Step 2: Add a custom mapping 1. Click **Add mapping**. 2. Pick the **entity type**: - **Contact** (most common) - **Company** (account-level attribution; SourceLoop aggregates from the company's contacts) - **Deal** (closed-won attribution; sourced from the contact that opened the deal) 3. Pick the **SourceLoop field** (left dropdown), e.g., `first_campaign`. 4. Pick the **HubSpot property** (right dropdown), e.g., `original_utm_campaign` (your existing property) or type a new property API name. 5. Pick the **direction**: - **Outbound** (SourceLoop → HubSpot) — for fields you want SourceLoop to own. - **Inbound** (HubSpot → SourceLoop) — for HubSpot data you want SourceLoop to import (e.g., HubSpot's own deal stage, owner, deal amount). - **Bidirectional** — both ways; the most-recent value wins. 6. Click **Save**. The new mapping takes effect on the next sync cycle (within 15 minutes). ## Step 3: Verify the mapping is working 1. Wait for one full sync cycle (~15 minutes) after a new conversion happens. 2. Open the contact in HubSpot. 3. Check the properties you mapped, the values should match what's on the contact in SourceLoop's Contacts Hub. If a property isn't getting written, see the troubleshooting checklist in [Troubleshoot HubSpot sync issues](/help/troubleshoot-hubspot-sync/). ## When to map to your own properties vs SourceLoop's defaults A simple rule of thumb: - **Map to SourceLoop defaults** when HubSpot is your CRM but you don't have UTM workflows already wired to other properties. The `sourceloop_*` properties keep the marketing data cleanly namespaced. - **Map to your own properties** when you've already built HubSpot workflows, lists, or reports against your own UTM fields (e.g., `original_utm_source`, `latest_utm_source`). Pointing SourceLoop at those fields means your existing automation just works. If you're not sure, start with the defaults. You can always add custom mappings later without breaking anything. ## Company and deal mappings Most teams stop at contact-level mappings. Two cases where company / deal mappings are useful: - **B2B with account-based reporting** — push first-touch source to the company record so your sales reports group by source at the account level (e.g., "all leads from this account came in via LinkedIn organic"). - **Closed-won attribution** — push the original contact's first-touch source to the deal record so HubSpot's revenue reports can group by source. Both use the same mapping flow, just pick the entity type accordingly. ## What's next Once your contact properties are flowing, the natural next step is mapping lifecycle stages and lead status so SourceLoop's internal pipeline aligns with HubSpot's. See [Map SourceLoop stages to HubSpot lifecycle stages](/help/map-hubspot-lifecycle-stages/). ## Frequently Asked Questions ### Do I need to create custom properties in HubSpot first? No. When SourceLoop's default mapping is enabled, it creates the required custom properties (`sourceloop_first_source`, `sourceloop_last_source`, the UTM properties, etc.) automatically the first time it tries to write them. If you'd rather map to your own existing properties, add the mapping manually in the Field Mapping tab and skip the auto-create. ### Can I push UTMs to the company record, not just the contact? Yes. Entity type 'company' is supported. Add a mapping with the entity set to 'company' and pick a HubSpot company property as the target. SourceLoop writes the company-level UTMs (aggregated from the company's contacts) on the next sync. ### Can I map the same SourceLoop field to two HubSpot properties? Yes. Add two mappings, same internal field, two different external fields. SourceLoop writes both on every sync. ### What's the difference between `first_campaign` and `latest_campaign` in the SourceLoop side? The `first_campaign` field is the UTM campaign of the visitor's very first session with you (first touch). The `latest_campaign` field is the UTM campaign of the session in which they actually converted (last touch, or "converting session"). Both are useful, last touch closes the deal, first touch shows what initially drove discovery. ### Can I prevent SourceLoop from overwriting a HubSpot property after the initial set? Yes. Set the field mapping's direction to **outbound only** and set the property's "write mode" to **on create**. SourceLoop populates it only on the first sync for that contact, then leaves it alone. ### What if my HubSpot custom property has a different label than the API name? SourceLoop's Field Mapping panel shows both the label and the API name. Map against the API name, which is what HubSpot actually uses to identify the property. Labels can change without breaking the mapping. --- # How to map HubSpot lifecycle stages and lead status Translate SourceLoop's internal lifecycle stages into HubSpot's lifecyclestage and hs_lead_status properties. Custom stages supported. Source: https://sourceloop.ai/help/map-hubspot-lifecycle-stages/ Updated: 2026-05-28 --- SourceLoop tracks visitors through internal lifecycle stages (New, Contacted, In Progress, Converted, Lost by default, plus any custom stages you add). HubSpot has its own vocabulary: the `lifecyclestage` property with 8 default values (subscriber, lead, marketingqualifiedlead, salesqualifiedlead, opportunity, customer, evangelist, other) plus `hs_lead_status` for finer-grained status inside the lead stages. This article walks through mapping the two together, both directions, so SourceLoop dashboards show your real funnel and HubSpot contacts show SourceLoop's stage as a property your reps can filter on. ## Before you start You'll need: - [HubSpot connected to SourceLoop](/help/connect-hubspot-to-sourceloop/) - **Admin** or **Owner** role in SourceLoop - A clear list of what your HubSpot lifecycle stages are (the standard 8, plus any custom ones you've added if you're on Pro+) ## How the mapping works The mapping is **per-connection** (one HubSpot account ↔ one set of mappings) and **bidirectional**: - **Inbound** (HubSpot → SourceLoop): when SourceLoop pulls a contact from HubSpot, it reads the `lifecyclestage` value (e.g., `salesqualifiedlead`) and translates it to the matching SourceLoop stage (e.g., `In Progress`). The mapped stage drives funnel dashboards in SourceLoop. - **Outbound** (SourceLoop → HubSpot): when a SourceLoop contact's stage changes (e.g., from `New` to `Converted` based on a Stripe charge), the mapping translates back to the HubSpot value (`customer`) and updates the contact property on HubSpot's side. Lead status maps the same way, just against `hs_lead_status` instead of `lifecyclestage`. ## Step 1: Open the HubSpot drawer 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> CRM -> HubSpot**. The drawer opens with the connection status, sync toggles, and two stacked sections relevant here: **Lead status mapping** and **Lifecycle stage mapping** (plus **Deal stages** and **Field mappings** below). ![SourceLoop HubSpot drawer showing the Lead status mapping (with New mapped) and Lifecycle stage mapping (with Subscriber) sections, each with an Edit button](/help/screenshots/sourceloop-hubspot-field-mapping.webp) 3. Each mapping section has its own **Edit** button on the right. Click **Edit** next to the section you want to configure. ## Step 2: Map HubSpot lifecycle stages to SourceLoop stages 1. Find each HubSpot lifecycle stage value in the left column. The defaults are: - `subscriber` — someone who opted in but hasn't engaged - `lead` — captured email - `marketingqualifiedlead` — engaged enough for marketing to consider qualified - `salesqualifiedlead` — handed off to sales - `opportunity` — open deal - `customer` — closed-won - `evangelist` — vocal advocate - `other` — fallback bucket 2. For each, pick the **matching SourceLoop stage** in the right-column dropdown. 3. A sensible default mapping: - `subscriber` → New - `lead` → Contacted - `marketingqualifiedlead` → In Progress - `salesqualifiedlead` → In Progress - `opportunity` → In Progress - `customer` → Converted - `evangelist` → Converted - `other` → (leave unmapped) 4. Click **Save**. If you've added custom lifecycle stages in HubSpot, they'll appear in the left column too. Map them to SourceLoop stages the same way. ## Step 3: Map HubSpot lead status to SourceLoop stages `hs_lead_status` is a finer-grained sub-status used while a contact is in the lead or SQL lifecycle stage. HubSpot's default values: - `NEW` - `OPEN` - `IN_PROGRESS` - `OPEN_DEAL` - `UNQUALIFIED` - `CONNECTED` - `BAD_TIMING` Map each one to a SourceLoop stage. A typical mapping: - `NEW` → New - `OPEN` → Contacted - `IN_PROGRESS` → In Progress - `OPEN_DEAL` → In Progress - `CONNECTED` → In Progress - `UNQUALIFIED` → Lost - `BAD_TIMING` → Lost Click **Save**. ## Step 4: Verify the mapping is taking effect 1. Wait 15 minutes for the next delta sync, or click **Resync now** on the HubSpot card. 2. Open a contact in SourceLoop's Contacts Hub. Its **Lifecycle stage** column should reflect the HubSpot value (translated to your SourceLoop stage). 3. Open the same contact in HubSpot. The `lifecyclestage` and `hs_lead_status` properties should show the values SourceLoop pushed. If a contact shows blank, see the troubleshooting article: [Troubleshoot HubSpot sync issues](/help/troubleshoot-hubspot-sync/). ## Custom SourceLoop stages The five default SourceLoop stages (New, Contacted, In Progress, Converted, Lost) cover most teams. If your funnel is more complex, you can add custom stages, for example, `Proposal`, `Negotiation`, `On hold`. 1. In SourceLoop, open **Settings -> Lifecycle stages**. 2. Click **Add stage**. 3. Name the stage and (optionally) mark it as "won" or "lost" for funnel reporting. 4. Save. The new stage appears in the dropdown on the HubSpot Stage Mapping panel. Map a HubSpot value to it (you may need a custom HubSpot lifecycle stage to map from for full round-trip support). ## What about deals? HubSpot deals have a different stage system (`dealstage`) scoped to deal pipelines. SourceLoop handles deal stages separately, on the inbound sync, deal pipelines and stages are pulled into SourceLoop's own pipeline tables for reporting. The Stage Mapping tab above is for **contact lifecycle**, not deal stages. If you want deal pipeline reporting in SourceLoop dashboards, just make sure inbound sync is enabled. Pipelines and stages flow in automatically; no per-pipeline mapping is needed because SourceLoop preserves your HubSpot pipeline structure as-is. ## What's next If sync is working but a specific contact isn't getting the mapped stage, the troubleshooting article walks through the common causes: [Troubleshoot HubSpot sync issues](/help/troubleshoot-hubspot-sync/). ## Frequently Asked Questions ### What's the difference between HubSpot's lifecycle stage and lead status? HubSpot's lifecycle stage is the broad funnel position (subscriber, lead, marketing qualified lead, sales qualified lead, opportunity, customer, evangelist, other). Lead status (`hs_lead_status`) is a finer-grained sub-status within the lead / SQL stages (New, Open, In Progress, Open Deal, Unqualified, Connected, Bad Timing). SourceLoop maps to both, but they're independent. ### Do I need HubSpot Sales Hub or Marketing Hub for lifecycle stages to work? No. Lifecycle stage is a standard property on every HubSpot account, including Free. HubSpot Sales Hub adds automation around stage transitions but the property itself works on every tier. ### Can I add custom lifecycle stages in HubSpot and map them? Yes. HubSpot lifecycle stages can be customised on Pro+ tiers. Add your custom stages in HubSpot first, then they'll appear in SourceLoop's stage-mapping dropdown the next time it syncs your HubSpot property schema. ### SourceLoop's internal stages are different from mine. Can I rename them? Yes. The five default SourceLoop stages (New, Contacted, In Progress, Converted, Lost) can be renamed and you can add custom ones. The mapping to HubSpot is per-connection, so renaming on SourceLoop's side doesn't break the mapping. ### What happens if SourceLoop sees a HubSpot stage I haven't mapped? The raw HubSpot value is preserved on the contact in SourceLoop's `lifecycle_stage_raw` field, but the contact's mapped `lifecycle_stage_id` stays NULL. You can map it later, and the next sync will pick up the mapping retroactively for all existing contacts in that stage. ### Does the mapping run on every sync or just on connect? Every sync. Each delta sync (every 15 minutes) re-applies the mapping for any contacts whose stage changed in HubSpot since the last run. So changes to the mapping take effect immediately, no full re-import needed. --- # How to troubleshoot HubSpot sync issues with SourceLoop Checklist for diagnosing HubSpot sync problems: missing contacts, stuck syncs, OAuth errors, scope rejections, and when to reconnect. Source: https://sourceloop.ai/help/troubleshoot-hubspot-sync/ Updated: 2026-05-28 --- If a HubSpot sync isn't behaving as expected, run through this checklist in order. Most problems fall into one of six buckets. ## Before you start Have these open: - **SourceLoop's HubSpot card** at **Setup -> CRM -> HubSpot** (showing **Last sync** timestamp + connection status) - **HubSpot's Connected Apps page** at **Settings -> Account -> Integrations -> Connected Apps** (to verify SourceLoop is authorised) - Optional: the **SourceLoop sync log** (three-dot menu on the HubSpot card) for the most recent error message ## Step 1: Check the connection status 1. Open **Setup -> CRM -> HubSpot** in SourceLoop. 2. Look at the card status: - **Connected** + recent **Last sync** timestamp → connection is healthy; the issue is downstream - **Connected** + stale **Last sync** (more than an hour ago) → the sync is stuck - **Token expired** → the OAuth token couldn't be refreshed; reconnect required - **Disconnected** → not connected; run the connect flow For each state, jump to the matching section below. ## Step 2: For 'Token expired' or 'Disconnected' 1. Click **Reconnect** on the HubSpot card. 2. SourceLoop runs the OAuth flow with HubSpot again. Sign in if prompted. 3. Authorise SourceLoop on HubSpot's consent screen. 4. You're returned to SourceLoop. The card flips to **Connected**. If reconnect fails immediately: - **"App not authorised"** → SourceLoop was removed from HubSpot's Connected Apps. The reconnect should re-add it. - **"User does not have access"** → the user authorising the connection needs Super Admin or Account Access permission in HubSpot. Sign in as a user who does. - **"Scope rejected"** → HubSpot blocked one or more requested scopes (rare; usually a HubSpot side restriction). Email hello@sourceloop.ai with the exact error. ## Step 3: For a stuck sync (Connected but Last sync is hours old) 1. Open the HubSpot card's three-dot menu and click **Sync log** (or **View errors**) to see the most recent failure. 2. Common errors and fixes: - **HTTP 401 (Unauthorized)** → token expired between scheduled runs. Click **Reconnect**. - **HTTP 403 (Forbidden)** → a scope you previously had is no longer granted. Reconnect with a user that has the missing scope. - **HTTP 429 (Rate limit exceeded)** → HubSpot is throttling. SourceLoop backs off automatically; the next scheduled run (within 15 minutes) usually picks back up. - **Network timeout** → temporary; will retry on the next cycle. If it persists for more than 24 hours, email us. 3. To force a sync now instead of waiting for the next scheduled cycle, click **Resync now** on the HubSpot card. ## Step 4: For missing contacts If sync looks healthy but specific contacts aren't appearing in SourceLoop or aren't getting SourceLoop properties written, walk through these: 1. **Email mismatch.** SourceLoop matches contacts to HubSpot by email. Open the contact in HubSpot and check the email is identical (case-insensitive) to what SourceLoop captured. Email aliases (e.g., `joe@example.com` vs `joe+website@example.com`) count as different contacts. 2. **Contact created before connection.** Initial sync only pulls contacts that have been modified since the connection was made. Old contacts with no recent activity may be skipped. Run **Initial sync** from the three-dot menu to pull every contact again. 3. **Exclude / include lists.** Some HubSpot accounts have list-based sync filters configured on SourceLoop. Open the HubSpot card and check the **Sync Filters** tab. 4. **HubSpot list membership.** If the contact is in a HubSpot list that's set as Exclude in the sync filters, they won't sync. ## Step 5: For "contacts sync but no properties update" If contacts are appearing but their UTM / source / lifecycle properties aren't getting populated: 1. Confirm **Outbound sync** is enabled on the HubSpot card. 2. Open the **Field Mapping** tab. Confirm at least one outbound mapping exists. See [Push UTMs to HubSpot](/help/push-utms-to-hubspot/) for what should be there by default. 3. For each mapping, confirm direction is **outbound** or **bidirectional**, not **inbound** (inbound mappings only read; they don't write). 4. Confirm the target HubSpot property actually exists. Open HubSpot at **Settings -> Properties** and search for the property's API name. If it doesn't exist, the auto-create may have failed (e.g., due to scope issues). Create it manually in HubSpot or remap to an existing property. ## Step 6: For "pipelines not loading" When inbound sync runs but pipelines and deal stages aren't appearing in SourceLoop dashboards: 1. The connecting HubSpot user needs `crm.objects.deals.read` and `crm.pipelines.deals.read` scopes. These are normally requested at OAuth time. If a HubSpot admin restricted these scopes for your user, you'll need to either get the restriction lifted or have someone else with full access run the reconnect. 2. Click **Reconnect** with a Super Admin user. ## Step 7: How to disconnect If you need to fully remove the HubSpot integration: 1. Open the HubSpot card in SourceLoop's CRM tab. 2. Click the three-dot menu and select **Disconnect**. 3. Confirm. SourceLoop stops syncing immediately, revokes the OAuth token, and clears the cached property schema. Your existing SourceLoop data (contacts, conversions, dashboards) stays intact, only future HubSpot syncs are paused. You can reconnect any time. For a full app removal on HubSpot's side, also go to **HubSpot Settings -> Integrations -> Connected Apps**, find SourceLoop, and click **Disconnect**. This is optional, the OAuth revocation from SourceLoop's side already invalidates the token. ## When to email support If you've worked through the checklist and the sync still isn't healthy, email **hello@sourceloop.ai** with: - The HubSpot card's current status - The most recent error message from the sync log - Your HubSpot portal ID (visible on the HubSpot card) - Approximate time the issue started We'll dig in from our side and respond within one business day. ## Frequently Asked Questions ### My HubSpot card shows 'Token expired'. What now? Click **Reconnect** on the card. SourceLoop runs the OAuth flow again and gets a fresh access + refresh token. Tokens usually rotate automatically (HubSpot access tokens last 30 minutes; SourceLoop uses the refresh token to renew them), but if the refresh token itself is revoked (e.g., someone removed SourceLoop from HubSpot Connected Apps), a manual reconnect is required. ### A specific contact isn't getting synced. What should I check? Three common causes. (1) Email mismatch, SourceLoop matches by email, so if the contact's email in HubSpot is different from what SourceLoop captured, they won't link. (2) Contact created before connection, the initial sync only pulls contacts modified since the connection, very old contacts may not have a recent modification date and be skipped. Run a manual full sync to pick them up. (3) Excluded by filter, check if you have any include/exclude lists configured on the HubSpot card. ### Sync says 'Pipelines not loaded'. Why? SourceLoop couldn't read your HubSpot pipeline schema. Most common cause is missing scope, the connecting user needs the `crm.objects.deals.read` and `crm.pipelines.deals.read` scopes. Disconnect and reconnect with a HubSpot user that has Super Admin permissions. ### My HubSpot connection works but no contact properties are being written. What's wrong? Outbound sync may be disabled. Open **Setup -> CRM -> HubSpot** and confirm **Outbound sync** is toggled on. If it's on but properties still aren't showing in HubSpot, check the Field Mapping tab, are there any mappings, and are they set to direction 'outbound' or 'bidirectional'? ### How do I do a full re-sync (pull everything again)? Open the HubSpot card, click the three-dot menu, and select **Run initial sync**. This bypasses the delta cursor and pulls every contact, company, and deal again. It's safe to run, all writes are idempotent. ### I'm getting 'Rate limit exceeded' from HubSpot. What now? HubSpot rate-limits API calls per account. SourceLoop respects the limits and backs off automatically. If you're seeing repeated rate-limit errors, check whether other tools (HubSpot's own backups, third-party sync tools, custom integrations) are sharing the same daily quota. ### How do I completely disconnect HubSpot from SourceLoop? Open the HubSpot card, click the three-dot menu, select **Disconnect**. Confirm. SourceLoop stops syncing immediately and the OAuth token is revoked. Your SourceLoop data stays intact, but no new HubSpot data will sync in until you reconnect. --- # How to disconnect HubSpot from SourceLoop Disconnect SourceLoop from your HubSpot account. What stops, what's deleted, what's retained, and how to request full data removal under GDPR. Source: https://sourceloop.ai/help/disconnect-hubspot-from-sourceloop/ Updated: 2026-05-28 --- This article covers the complete HubSpot disconnect flow, what happens immediately, what data we retain, what we delete, and how to request full data removal under GDPR. It exists to give you (and your data-protection officer, if you have one) a clear answer to every reasonable question about what SourceLoop does with HubSpot-sourced data after you decide to disconnect. ## Before you start A clear-headed expectation about what disconnect does and doesn't do: - **Disconnecting on SourceLoop's side** revokes OAuth access and stops syncing. - **Disconnecting on HubSpot's side** removes SourceLoop from your Connected Apps list (recommended for clean cleanup, but optional). - **Neither action deletes data from HubSpot itself**, SourceLoop has never had delete permissions, and disconnect doesn't trigger any kind of data cleanup on HubSpot's end. - **Existing SourceLoop data stays in your workspace** unless you explicitly request deletion via GDPR. ## Step 1: Disconnect on SourceLoop's side 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> CRM** in the left sidebar and click the **HubSpot** card. The HubSpot drawer slides in from the right. 3. Click the **Disconnect** button in the top-right of the drawer (next to **Resync**). ![SourceLoop HubSpot connection drawer showing the Resync and Disconnect buttons at the top right with a red arrow pointing to Disconnect, plus the connection details, lead status mapping, lifecycle stage mapping, deal stages, and field mappings underneath](/help/screenshots/sourceloop-hubspot-disconnect.png) 4. Confirm the disconnect dialog. The moment you confirm: - SourceLoop **revokes the OAuth access and refresh tokens** stored in our database. - SourceLoop **stops scheduling sync jobs** for this connection. - The HubSpot card on the CRM tab flips to **Disconnected**. No additional HubSpot API calls are made by SourceLoop after this point. ## Step 2: Disconnect on HubSpot's side (recommended) This step is optional but recommended for completeness. It removes SourceLoop from your HubSpot Connected Apps list, which: - Provides a paper trail in HubSpot's audit log - Prevents accidental reconnect via a cached OAuth flow - Is the cleanest state for GDPR / SOC2 records 1. Sign in to HubSpot. 2. Open **Settings (gear icon) -> Account Setup -> Integrations -> Connected Apps**. 3. Find **SourceLoop** in the list. 4. Click the three-dot menu and select **Disconnect**. 5. Confirm. HubSpot revokes its end of the OAuth relationship. Even if SourceLoop had any cached tokens, they'd be invalid from this moment. ## Step 3: Decide what to do with the SourceLoop custom properties SourceLoop created the following custom properties on your HubSpot Contact (and Company, Deal) objects during normal operation: - `sourceloop_first_source`, `sourceloop_first_medium`, `sourceloop_first_campaign`, `sourceloop_first_content`, `sourceloop_first_term` - `sourceloop_first_landing_page`, `sourceloop_first_channel` - `sourceloop_latest_source`, `sourceloop_latest_medium`, `sourceloop_latest_campaign`, `sourceloop_latest_content`, `sourceloop_latest_term` - `sourceloop_latest_landing_page`, `sourceloop_latest_channel` - `sourceloop_id` (internal mapping ID) After disconnect, these properties **stay in your HubSpot account** with their last-synced values. SourceLoop does not, and cannot, delete them automatically. You have two options: - **Leave them** (recommended). They don't affect HubSpot's behavior. If you reconnect later, SourceLoop picks them back up automatically, no recreation needed. - **Delete them manually**. Inside HubSpot, go to **Settings -> Properties -> Contact properties** (and **Company / Deal properties**), filter by "sourceloop", and delete each. **Warning:** this also deletes the data stored in those properties on every contact. Once deleted in HubSpot, the values are gone, even if you later reconnect. ## What SourceLoop retains after you disconnect For transparency, here's exactly what SourceLoop keeps on its side after a HubSpot disconnect: ### Deleted at the moment of disconnect - HubSpot OAuth access token (encrypted at rest) - HubSpot OAuth refresh token (encrypted at rest) - Scheduled sync jobs for the connection - Properties cache (HubSpot custom property schema) ### Retained for 30 days, then permanently deleted - The connection record (portal ID, last-sync cursor, sync-in-progress flag) - Field mapping rules you configured (so reconnect can resume them) - Stage mapping rules you configured - The most recent 30 days of sync logs (operational debugging) This 30-day grace window exists so that an accidental disconnect can be undone by reconnecting without losing your mapping configuration. After 30 days, the connection record and all associated configuration are purged from our database. Reconnecting after that point creates a fresh connection record. ### Retained indefinitely (until you request deletion) - **Contacts and companies that originated from HubSpot inbound sync.** Once synced, these records are part of your SourceLoop workspace data. They contain your business's customer information and are governed by your SourceLoop workspace's data policy, not the HubSpot connection. - **Conversion records, journeys, and attribution.** These are SourceLoop-owned data records that reference the HubSpot contact via email/external ID. - **Aggregated and anonymized analytics** that contributed to dashboards and reports. These retained records can be deleted on request, see "GDPR / full data removal" below. ## GDPR / full data removal If you want SourceLoop to completely remove all data we ever ingested from your HubSpot account, including contacts, companies, deals, sync logs, and any derived analytics: 1. Email **hello@sourceloop.ai** with the subject "GDPR data deletion request" and your HubSpot portal ID. 2. Confirm your identity (we use the authenticated SourceLoop account email or a verified workspace owner email). 3. We acknowledge the request within 2 business days. 4. We complete the deletion within 30 days and confirm in writing. We'll delete: - The HubSpot connection record (if it hasn't been auto-purged already) - All contact and company records ingested from HubSpot - All conversion records and journeys tied to those contacts - All sync logs, error logs, and configuration history - All custom analytics derived from HubSpot data This is irreversible. After completion, reconnecting HubSpot starts from a blank state, the contacts and history we deleted cannot be restored. ## Reconnecting later If you change your mind (or accidentally disconnected) within the 30-day grace window: 1. Sign in to SourceLoop. 2. Open **Setup -> CRM -> HubSpot**. 3. Click **Connect** on the HubSpot card. 4. Run the OAuth flow with HubSpot. The same scopes are requested. 5. SourceLoop resumes syncing from where it left off, with your field and stage mappings preserved. If you reconnect after the 30-day window, the connection record is fresh. Mappings need to be configured from scratch. Existing SourceLoop contacts (still in your workspace) get re-stitched to the matching HubSpot contacts on the next sync. ## When to email support For anything outside the standard disconnect / reconnect / GDPR deletion flow: - "I disconnected by accident and want a full reset" → email hello@sourceloop.ai - "We have a data-protection officer review" → we can provide a written data-processing agreement (DPA) and a detailed audit trail of what was retained vs. deleted on your specific connection - "Our HubSpot admin says SourceLoop is still listed even though I disconnected" → check the HubSpot Connected Apps page directly; if it's still there, complete Step 2 above Email **hello@sourceloop.ai** for any of the above. We respond within one business day. ## Frequently Asked Questions ### How quickly does SourceLoop stop accessing my HubSpot account after I disconnect? Immediately. The OAuth access and refresh tokens are revoked on SourceLoop's side at the moment you click Disconnect. No further API calls are made to HubSpot from your connection after that point. ### Will my HubSpot data be deleted from HubSpot? No. SourceLoop never deletes data from your HubSpot account, before, during, or after disconnect. Disconnecting only stops SourceLoop's read and write access to your HubSpot account. Everything that exists in HubSpot stays there exactly as it was. ### What about the SourceLoop custom properties (sourceloop_first_source, etc.) that were created in HubSpot? They stay in HubSpot after disconnect, with their last-synced values intact. Removing them requires manual cleanup inside HubSpot at Settings -> Properties. We recommend leaving them, they don't affect HubSpot's behavior, and if you reconnect later, the existing properties get picked up automatically without needing to be recreated. ### What data does SourceLoop retain on its side after disconnect? Your SourceLoop workspace, contacts, conversions, dashboards, and historical attribution stay intact. The HubSpot-specific connection record (OAuth tokens, field mappings, stage mappings, last-sync cursor) is kept for 30 days in case you reconnect, after which it's permanently deleted. Contact records that originated from HubSpot inbound sync stay in your workspace because they're now part of your SourceLoop dataset. ### How do I request full deletion of my HubSpot-sourced data from SourceLoop (GDPR)? Email hello@sourceloop.ai with the subject "GDPR data deletion request" and the HubSpot portal ID. We delete the connection record and all data SourceLoop ingested from that HubSpot account (contacts, companies, deals, pipelines, stages, sync logs) within 30 days, per our GDPR commitment. We confirm completion in writing. ### Can I reconnect HubSpot later without losing my historical SourceLoop data? Yes, as long as you reconnect within the 30-day connection-record retention window. The same OAuth flow runs, and SourceLoop resumes syncing from the last cursor. After 30 days the connection record is purged, so reconnecting after that creates a fresh connection record (your other SourceLoop data, contacts, dashboards, journeys, is still intact). ### Does disconnecting affect my SourceLoop subscription or billing? No. Disconnecting HubSpot is independent of your SourceLoop subscription. Billing continues per your plan. Other integrations (Salesforce, Pipedrive, payments, forms, chats) are unaffected. --- # How to track lead source in Salesforce Push UTMs, lead source, and the full visitor journey into Salesforce Lead, Contact, Account, and Opportunity records. Sandbox and production. Source: https://sourceloop.ai/help/connect-salesforce-to-sourceloop/ Updated: 2026-05-28 --- Salesforce is the system of record for most B2B sales teams, but Salesforce's native attribution stops at a single `LeadSource` field with no multi-touch model and no historical journey. SourceLoop adds: first-touch and last-touch source on every Lead and Contact, the full UTM trail, the visitor's pre-conversion journey, and multi-touch attribution models that respect Salesforce's Lead → Contact → Opportunity flow. This article covers the OAuth connect flow only. After connecting, see: - [Push UTMs and lead source to Salesforce fields](/help/push-utms-to-salesforce/) - [Map SourceLoop stages to Salesforce Lead Status](/help/map-salesforce-lead-status/) ## Why connect Salesforce to SourceLoop? Salesforce ships with one `LeadSource` field per Lead. SourceLoop adds: - **First-touch source / medium / campaign** as separate fields, so you know the original channel even after multiple touchpoints - **Last-touch (converting session) source / medium / campaign** as separate fields - **Full UTM trail** with content and term, not just source and medium - **Visitor journey** (every page browsed pre-conversion) viewable on the Lead / Contact record - **Multi-touch attribution models** (first, last, linear, position-based, time-decay) - **Revenue-by-channel** when payment integrations are connected, attributed back to the original Lead All of this sits on standard Salesforce records, so it shows up in Salesforce reports, Lightning record pages, and any workflow / process you build against it. ## Before you start You'll need: - A **SourceLoop workspace** with the [tracking pixel installed](/help/install-the-tracking-pixel/) - A **Salesforce org** (Sales Cloud or Service Cloud; any edition) - A Salesforce user with **System Administrator** profile, or Modify All Data + API Enabled permissions, to run the OAuth connect - **Admin** or **Owner** role in SourceLoop ## Step 1: Open the CRM integrations page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Setup** in the left sidebar. ![SourceLoop Home page with the Setup entry in the left sidebar highlighted](/help/screenshots/sourceloop-setup-navigation.webp) 3. Click the **CRM** tab inside Setup. ![SourceLoop Setup CRM page showing the supported CRM provider cards including HubSpot, Salesforce, and Pipedrive](/help/screenshots/sourceloop-crm-integration-page.webp) 4. Scroll to the **Salesforce** card. ![SourceLoop CRM drawer with the Salesforce Connect button and sandbox toggle highlighted](/help/screenshots/sourceloop-connect-salesforce.webp) ## Step 2: Pick sandbox or production 1. On the Salesforce card, toggle **Sandbox** ON if you want to connect to your sandbox org first (recommended for first-time setups). 2. Leave it OFF to connect to production. 3. Click **Connect**. You'll be redirected to Salesforce's OAuth consent screen, either `login.salesforce.com` (production) or `test.salesforce.com` (sandbox). > **Connect to sandbox first** > Most teams connect to a Salesforce sandbox first, validate the field mappings and stage mappings against test data, and then connect production once the mapping is dialed in. Sandbox and production connections live side-by-side in SourceLoop, you can have both at once. ## Step 3: Authorise SourceLoop on Salesforce 1. Sign in to Salesforce if you aren't already. 2. Pick the org you want to connect (if you have multiple Salesforce users). 3. Review the scopes SourceLoop is requesting (API access, refresh_token, offline_access). 4. Click **Allow**. Salesforce redirects you back to SourceLoop. The connection card flips to **Connected** and shows your Salesforce instance URL (e.g., `acme.my.salesforce.com`). > **Salesforce tokens auto-refresh** > Salesforce OAuth access tokens are 2-hour-lived. SourceLoop stores the refresh token and rotates the access token automatically, no manual reconnect needed unless you revoke the connection in Salesforce. ## Step 4: Enable sync direction After connecting, the Salesforce drawer opens with sync settings: 1. **Inbound sync** (Salesforce → SourceLoop): pulls Leads, Contacts, Accounts, and Opportunities into SourceLoop so dashboards reflect your real pipeline. 2. **Outbound sync** (SourceLoop → Salesforce): pushes UTM, source, and journey data from SourceLoop into Salesforce fields on Lead and Contact records. Most teams enable both. Click **Save** to start the first sync. ## Step 5: Wait for the initial sync The initial sync pulls Salesforce records into SourceLoop. Duration depends on volume: - Small org (under 10k Leads + Contacts): under 5 minutes - Mid-size (10k-100k): 10-30 minutes - Large org (100k+): up to a few hours The sync runs server-side, you don't need to keep SourceLoop open. Watch the **Last sync** timestamp on the Salesforce card. After the initial run, deltas sync every 15 minutes automatically. ## What gets written to Salesforce Out of the box, SourceLoop creates and writes to these custom fields on the **Lead** object (and mirrored on **Contact** via Salesforce's lead-conversion mapping): - `sourceloop_first_source__c`, `sourceloop_first_medium__c`, `sourceloop_first_campaign__c`, etc. - `sourceloop_latest_source__c`, `sourceloop_latest_medium__c`, `sourceloop_latest_campaign__c`, etc. - `sourceloop_first_landing_page__c`, `sourceloop_latest_landing_page__c` - `sourceloop_id__c` — internal link (don't delete) These fields are created automatically on first sync, you don't need to pre-create them in Salesforce. For mapping to your own existing custom fields (or to standard fields like `LeadSource`), see [Push UTMs and lead source to Salesforce fields](/help/push-utms-to-salesforce/). ## What's next - **Field mapping** — by default SourceLoop maps to its own `sourceloop_*` fields. To map to your existing fields or push to specific custom objects, see [Push UTMs to Salesforce](/help/push-utms-to-salesforce/). - **Lead Status mapping** — translate SourceLoop's internal lifecycle stages to Salesforce's `Lead Status` picklist values. See [Map SourceLoop stages to Salesforce Lead Status](/help/map-salesforce-lead-status/). - **Troubleshooting** — if a sync looks stuck or fields aren't writing, see [Troubleshoot Salesforce sync issues](/help/troubleshoot-salesforce-sync/). ## Frequently Asked Questions ### Do I need Salesforce Sales Cloud or Service Cloud? Either works. SourceLoop reads and writes against standard Salesforce objects (Lead, Contact, Account, Opportunity) which exist in every edition. Marketing Cloud is not required. ### Does this work with a Salesforce Sandbox? Yes. There's a sandbox toggle on the Salesforce connect screen. Flip it on, OAuth goes through test.salesforce.com instead of login.salesforce.com, and SourceLoop hits your sandbox endpoints. Sandbox and production connections are separate, you can have both at once. ### Will SourceLoop overwrite my existing Salesforce Lead Source field? Only if you explicitly map SourceLoop's first-touch source field to Salesforce's standard `LeadSource` field. By default, SourceLoop writes to its own custom field (`sourceloop_first_source__c`) so your existing LeadSource values are preserved. You can change the mapping in the Field Mapping tab. ### How does SourceLoop handle Lead conversion to Contact / Account / Opportunity? When a Salesforce Lead is converted, the source and UTM data on the Lead are carried onto the resulting Contact, Account, and Opportunity via Salesforce's standard conversion mapping. SourceLoop's custom fields are mapped on both Lead and Contact, so the attribution survives the conversion process. ### I use Lightning Experience. Does that matter? No. SourceLoop uses the Salesforce REST and Bulk APIs, which are UI-agnostic. Both Lightning and Classic users see the same SourceLoop data on records once it's synced in. ### Can I sync multiple Salesforce orgs? One Salesforce org per SourceLoop website. For multiple orgs, create a separate SourceLoop website for each and connect them independently. --- # How to push UTM parameters to Salesforce Map SourceLoop UTM and source fields to Salesforce Lead, Contact, Account, and Opportunity records, including the standard LeadSource field. Source: https://sourceloop.ai/help/push-utms-to-salesforce/ Updated: 2026-05-28 --- By default, SourceLoop ships with a mapping that writes UTM and source data to `sourceloop_*` custom fields on the Lead and Contact objects. For most teams that's enough. For teams that need to populate the standard `LeadSource` field, map to existing custom fields, or push to Accounts and Opportunities, the Field Mapping tab supports it. This article walks through the mapping system end to end. ## Before you start You'll need: - [Salesforce connected to SourceLoop](/help/connect-salesforce-to-sourceloop/) - A Salesforce user with **Modify All Data** (or System Administrator profile) for the auto-create flow - **Admin** or **Owner** role in SourceLoop ## The fields SourceLoop can push Same fields as every CRM integration: **Contact identity:** email, contact name, phone, company name, country, city, title, LinkedIn URL **Attribution (per touchpoint):** - First-touch: source, medium, campaign, content, term, landing page, channel, keyword - Last-touch (converting session): same set **Lifecycle:** lead status (raw + mapped), lifecycle stage (raw + mapped), lead score, qualified flag **Revenue:** quote value, sales value ## Default mapping When you connect Salesforce and enable outbound sync, SourceLoop sets up these default mappings on the **Lead** object: - `first_source` → `sourceloop_first_source__c` - `first_medium` → `sourceloop_first_medium__c` - `first_campaign` → `sourceloop_first_campaign__c` - `first_landing_page` → `sourceloop_first_landing_page__c` - `latest_source` → `sourceloop_latest_source__c` - `latest_medium` → `sourceloop_latest_medium__c` - `latest_campaign` → `sourceloop_latest_campaign__c` - `latest_landing_page` → `sourceloop_latest_landing_page__c` - `lead_status_raw` → `Status` (standard Salesforce Lead Status picklist) Same set is mirrored on the **Contact** object so that data survives Lead conversion. These fields are created on the Salesforce side automatically the first time SourceLoop writes to them, no manual setup required (provided the connecting user has create-custom-field permission). ## Step 1: Open the Salesforce Field Mapping tab 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> CRM -> Salesforce**. 3. Click the **Field Mapping** tab inside the drawer. You'll see existing mappings (defaults + any you've added), an **Add mapping** button, and a Salesforce field picker that lists every standard and custom field SourceLoop's introspection picked up. ## Step 2: Add a custom mapping 1. Click **Add mapping**. 2. Pick the **entity type**: - **Lead** (the most common starting point) - **Contact** (for after conversion) - **Account** (account-level rollup) - **Opportunity** (closed-won attribution) 3. Pick the **SourceLoop field** (left dropdown), e.g., `first_source`. 4. Pick the **Salesforce field** (right dropdown), e.g., `LeadSource` (standard) or `Original_UTM_Source__c` (custom). 5. Pick the **direction**: - **Outbound** (SourceLoop → Salesforce) — for fields SourceLoop owns. - **Inbound** (Salesforce → SourceLoop) — pull existing Salesforce values into SourceLoop (e.g., Opportunity Amount, Owner, Stage). - **Bidirectional** — both ways; most-recent value wins. 6. Click **Save**. New mapping takes effect on the next sync cycle (within 15 minutes). ## Step 3: Configure Salesforce's Lead → Contact conversion mapping This is the most-missed step. Salesforce's standard Lead conversion only copies custom fields from Lead to Contact if you've explicitly configured the mapping. Without it, your `sourceloop_first_source__c` value on the Lead gets dropped when the Lead converts to a Contact. 1. In Salesforce, go to **Setup -> Object Manager -> Lead -> Fields & Relationships -> Map Lead Fields**. 2. For each `sourceloop_*` custom field on Lead, map it to its same-named counterpart on Contact. 3. Save. Now your attribution carries through every conversion. If you converted Leads before doing this, the historical conversions won't have the source data on the Contact, but every conversion going forward will. ## Step 4: Verify the mapping is working 1. Wait for one full sync cycle (~15 minutes) after a new conversion happens. 2. Open the Lead in Salesforce. 3. Check the `sourceloop_*` custom fields and any standard fields you mapped, values should match what's shown on the contact in SourceLoop's Contacts Hub. If a field isn't getting written, see [Troubleshoot Salesforce sync issues](/help/troubleshoot-salesforce-sync/). ## Should you also map to Salesforce's standard LeadSource? A common question. Two camps: - **Don't map** (default behavior): SourceLoop writes to `sourceloop_*` fields. Your team's existing LeadSource conventions stay intact. Reports built on LeadSource keep working unchanged. SourceLoop adds new fields you can pivot on separately. - **Do map**: SourceLoop writes first-touch source to `LeadSource` directly. Useful when your existing Salesforce reports are heavily built on LeadSource and changing them is more work than letting SourceLoop populate it. Caveat: SourceLoop will overwrite any prior LeadSource value (including manual entry). There's no wrong answer, it depends on how much existing Salesforce reporting depends on LeadSource. Most teams start with the default and add the LeadSource mapping later if reporting is awkward. ## Pushing to custom objects Custom objects (e.g., `Custom_Application__c`) aren't supported via the Field Mapping tab. For those, use the Outbound Webhook (Setup -> Integrations -> Webhooks) to send raw conversion events as JSON and write to the custom object via an Apex trigger or Flow on your side. ## What's next After fields are mapped, the next step is aligning SourceLoop's lifecycle stages with Salesforce's Lead Status picklist. See [Map SourceLoop stages to Salesforce Lead Status](/help/map-salesforce-lead-status/). ## Frequently Asked Questions ### Do I need to create custom fields in Salesforce first? No. SourceLoop auto-creates the default `sourceloop_*` custom fields on the Lead and Contact objects the first time it tries to write them. The connecting Salesforce user needs Modify All Data + the ability to create custom fields. If you'd rather map to existing fields, add the mapping in the Field Mapping tab and skip the auto-create. ### Should I map first-touch source to Salesforce's standard LeadSource field? Personal preference. Mapping to `LeadSource` means SourceLoop overwrites whatever was there (manual entry, web-to-lead default, etc.). If your team already uses LeadSource for high-level categorisation (Web, Partner, Trade Show), it's safer to leave it alone and map SourceLoop to `sourceloop_first_source__c` so both values coexist. Some teams do map to LeadSource specifically so existing Salesforce reports work without modification. ### Can I push UTMs to the Account record, not just Lead and Contact? Yes. Add a mapping with entity type 'account' and pick the Salesforce Account field as the target. SourceLoop aggregates UTM values across all Contacts on the Account and writes the consensus value. ### What about pushing to Opportunity? Yes. Opportunity-level attribution is supported. The source values come from the converting Lead / Contact (the person who opened the Opportunity), and SourceLoop writes them onto the Opportunity record on each sync. ### Does the mapping survive Salesforce's Lead → Contact conversion? Yes, with one caveat. Salesforce's standard conversion only carries fields you've explicitly mapped in its Lead Convert Settings (Setup -> Customize -> Leads -> Lead Custom Field Mapping). After SourceLoop auto-creates `sourceloop_*` fields on both Lead and Contact, you should configure Salesforce's conversion mapping to copy each `sourceloop_*` field from Lead to its Contact counterpart. SourceLoop's docs panel in the Salesforce drawer has a step-by-step on this. ### My custom field has a Field-Level Security restriction. Will SourceLoop be able to write? SourceLoop writes via the OAuth-connected user. If that user's profile doesn't have edit access to the field, the write fails silently. Either grant the user FLS edit access, or connect with a System Administrator user. --- # How to map Salesforce Lead Status Translate SourceLoop's internal lifecycle stages into Salesforce's Lead Status picklist values. Both directions, custom picklist values supported. Source: https://sourceloop.ai/help/map-salesforce-lead-status/ Updated: 2026-05-28 --- Salesforce orgs use the `Status` picklist on the Lead object to track lead state (Open, Working, Converted, Unqualified, plus custom values your team has added). SourceLoop has its own internal lifecycle stages (New, Contacted, In Progress, Converted, Lost). This article walks through aligning the two so SourceLoop dashboards reflect your Salesforce pipeline and Salesforce records show the SourceLoop stage as data your reps can filter on. ## Before you start You'll need: - [Salesforce connected to SourceLoop](/help/connect-salesforce-to-sourceloop/) - **Admin** or **Owner** role in SourceLoop - A list of your **Lead Status picklist values** (visible in Salesforce at Setup -> Object Manager -> Lead -> Fields & Relationships -> Lead Status) ## How the mapping works The Lead Status mapping is **per-connection** and **bidirectional**: - **Inbound** (Salesforce → SourceLoop): when a Lead is pulled in, SourceLoop reads its `Status` value (e.g., `Working`) and translates it to the matching SourceLoop stage (e.g., `In Progress`). The mapped stage drives funnel dashboards in SourceLoop. - **Outbound** (SourceLoop → Salesforce): when a SourceLoop contact's stage changes (e.g., based on a Stripe payment), the mapping translates back to a Salesforce picklist value and updates the Lead's `Status` field. ## Step 1: Open the Stage Mapping panel 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> CRM -> Salesforce**. 3. Click the **Stage Mapping** tab inside the drawer. The panel shows your Salesforce Lead Status values in the left column, each with a dropdown of SourceLoop stages on the right. ## Step 2: Map each Salesforce Lead Status to a SourceLoop stage A typical mapping for an org using Salesforce's standard values: - `Open - Not Contacted` → New - `Working - Contacted` → Contacted - `Closed - Converted` → Converted - `Closed - Not Converted` → Lost For an org with custom values (e.g., `MQL`, `SQL`, `Working`, `Nurturing`): - `MQL` → Contacted - `SQL` → In Progress - `Working` → In Progress - `Nurturing` → In Progress - `Disqualified` → Lost For each row, pick the matching SourceLoop stage and the mapping saves automatically on selection. > **Many-to-one is the norm** > Salesforce orgs often have 8-15 Lead Status values for granular pipeline reporting; SourceLoop's 5 default stages cover the broader funnel. Mapping several Salesforce values to one SourceLoop stage is normal and recommended, the mapping is for analytics aggregation, not granular state. ## Step 3: Map Converted leads carefully Salesforce's `IsConverted` flag is separate from `Status` — a Lead can be marked Converted via Lead conversion (which creates a Contact, Account, and optionally Opportunity) independently of its Status picklist value. For revenue and funnel analytics in SourceLoop, the practical rule: - Map any `Closed - Converted` or `Qualified` Status values to SourceLoop's **Converted** stage - Map any `Closed - Not Converted`, `Disqualified`, or `Unqualified` Status values to SourceLoop's **Lost** stage - Leave any in-progress Status values mapped to **In Progress** so they appear in active funnel reports ## Step 4: Verify the mapping is taking effect 1. Wait 15 minutes for the next delta sync (or click **Resync now** on the Salesforce card). 2. Open a Lead in SourceLoop's Contacts Hub. Its **Lifecycle stage** should reflect the mapped Salesforce value. 3. Update the Lead's `Status` in Salesforce. Within 15 minutes the SourceLoop contact's stage updates to match. If the stage stays blank, see [Troubleshoot Salesforce sync issues](/help/troubleshoot-salesforce-sync/). ## Mapping a custom Lifecycle Stage field (not Lead Status) If your Salesforce org has a custom field like `Lifecycle_Stage__c` (some orgs add this to mirror HubSpot's concept), SourceLoop can map to it instead of (or in addition to) the standard `Status` field. 1. In the Stage Mapping tab, click **Change target field**. 2. Pick your custom field (e.g., `Lifecycle_Stage__c`) from the dropdown of available picklist fields. 3. Save. 4. Map each picklist value the same way as in Step 2. You can have both `Status` and `Lifecycle_Stage__c` mapped at once; SourceLoop writes both. ## Opportunity Stage (a separate flow) Opportunity Stage is **not** mapped via this panel. Salesforce Opportunities use multi-pipeline stage flows that vary by record type, and SourceLoop preserves your existing pipeline structure as-is, no per-stage mapping is needed. On inbound sync, Opportunities flow into SourceLoop with their current Stage and pipeline. Funnel reports respect the Salesforce stage order automatically. ## What's next If the mapping isn't behaving as expected, the troubleshooting article walks through the common causes: [Troubleshoot Salesforce sync issues](/help/troubleshoot-salesforce-sync/). ## Frequently Asked Questions ### Does Salesforce have a lifecycle stage field like HubSpot? Not natively. Salesforce's closest equivalent is the Lead Status picklist (`Status` field on Lead). Some orgs add a custom field like `Lifecycle_Stage__c` to mirror HubSpot's concept; SourceLoop can map to either. The Stage Mapping tab in the Salesforce drawer lets you pick which Salesforce field to use. ### My team uses Salesforce's standard Lead Status (Open, Working, Converted, Unqualified). Can I map to those? Yes, that's the most common case. SourceLoop's Stage Mapping panel pre-loads your org's Lead Status picklist values from Salesforce's Describe API, so you'll see exactly what your team uses (including any custom values). ### What about Opportunity Stage? Can SourceLoop map to it? Opportunity Stage is handled separately. SourceLoop pulls Opportunity records and their stages on inbound sync; the standard Opportunity stages flow into SourceLoop dashboards automatically. No manual mapping needed because Opportunities use Salesforce's own multi-pipeline stage flow, which SourceLoop preserves as-is. ### If my Salesforce admin adds a new Lead Status value, do I need to update the mapping? Yes. New picklist values appear in SourceLoop's Stage Mapping dropdown after the next properties cache refresh (every 24 hours, or after a manual reconnect). Map the new value to a SourceLoop stage and save. ### What happens if SourceLoop sees a Salesforce Lead Status I haven't mapped? The raw Salesforce value is preserved on the contact in `lead_status_raw`, but the mapped `lead_status_id` stays NULL. You can add the mapping later, and the next sync re-applies it. ### Can I map multiple Salesforce Lead Status values to the same SourceLoop stage? Yes. SourceLoop's stages are a fixed funnel (New → Contacted → In Progress → Converted / Lost) while Salesforce orgs often have many granular statuses. Mapping multiple Salesforce values to one SourceLoop stage (e.g., 'Working', 'Nurturing', 'Awaiting Response' all → 'In Progress') is the standard pattern. --- # How to troubleshoot Salesforce sync issues with SourceLoop Checklist for diagnosing Salesforce sync problems: OAuth errors, FLS restrictions, sandbox mix-ups, missing records, and clean disconnects. Source: https://sourceloop.ai/help/troubleshoot-salesforce-sync/ Updated: 2026-05-28 --- Salesforce sync issues fall into a handful of buckets. This checklist runs through them in order so you can get from "something's off" to a working sync in under fifteen minutes. ## Before you start Have these tabs open: - **SourceLoop's Salesforce card** at **Setup -> CRM -> Salesforce** - **Salesforce's Connected Apps OAuth Usage page** (Setup -> Connected Apps -> Connected Apps OAuth Usage) - The most recent **Sync log** entry on the Salesforce card (three-dot menu) ## Step 1: Check the connection status 1. Open **Setup -> CRM -> Salesforce** in SourceLoop. 2. Look at the card: - **Connected** + recent **Last sync** → healthy - **Connected** + stale **Last sync** (>1 hour) → stuck - **Token expired** → reconnect needed - **Disconnected** → run connect flow ## Step 2: For 'Token expired' / 'Disconnected' 1. Click **Reconnect**. 2. Sign in to Salesforce (production or sandbox, matching the toggle on the card). 3. Authorise the scopes. 4. You're returned to SourceLoop, card flips to **Connected**. If reconnect fails: - **"User not authorised"** → the user authorising doesn't have API access (Profile -> Administrative Permissions -> API Enabled). - **"insufficient_access_or_readonly"** → the user's profile doesn't have Modify All Data; either grant it or reconnect with a System Administrator. - **"Session expired or invalid"** → Salesforce session timeout. Sign in fresh and retry. ## Step 3: For a stuck sync 1. Open the Salesforce card's three-dot menu and click **Sync log**. 2. Common errors and fixes: - **`INVALID_SESSION_ID`** → token expired between cycles. Click **Reconnect**. - **`REQUEST_LIMIT_EXCEEDED`** → daily API call quota hit. SourceLoop backs off; the next day's quota refreshes automatically. Mid-day fix: have a Salesforce admin check daily API usage at Setup -> System Overview, and look for other tools consuming the quota. - **`API_DISABLED_FOR_ORG`** → API access turned off org-wide. Re-enable in Setup -> Profiles or Permission Sets. - **`INVALID_FIELD_FOR_INSERT_UPDATE`** → a mapped field doesn't exist or isn't writable. See the FAQ above. 3. To force a sync immediately, click **Resync now**. ## Step 4: For missing records If Leads, Contacts, or Opportunities aren't appearing in SourceLoop: 1. **Email mismatch.** SourceLoop matches by email. Confirm the record has an Email value and that it matches what SourceLoop captured. 2. **Pre-connection records.** Initial sync only pulls records modified after the connection date. Run **Initial sync** from the three-dot menu to pull everything. 3. **Sharing rules / OWD.** The connecting user must have Read access to the record. Test by viewing the record as that user; if they can't see it, neither can SourceLoop. Either change OWD or grant the user broader sharing. 4. **Org-Wide Defaults.** If your org sets Lead Public to Read-Only, the connecting user needs to be in the right role hierarchy. The fastest fix is connecting with a System Administrator user. ## Step 5: For 'fields not writing' (records sync in but properties stay blank) 1. **Outbound sync** is enabled on the Salesforce card. 2. **Field-Level Security**. Open Salesforce Setup -> Profiles -> [user's profile] -> Object Settings -> Lead, and confirm Edit access on every `sourceloop_*` field. Apply the same on Contact. 3. **Field Mapping** tab has at least one outbound mapping; direction is **outbound** or **bidirectional**. 4. **Field exists**. Use Salesforce's Object Manager to confirm the target field (e.g., `sourceloop_first_source__c`) exists on the Lead object. 5. **Validation rules.** If a validation rule is rejecting the write (e.g., requires a non-empty `LeadSource` first), the sync log will show it. Either adjust the validation rule or pre-populate the required field. ## Step 6: For 'auto-create of sourceloop_* fields failed' If the initial sync runs but SourceLoop didn't auto-create the custom fields: 1. The connecting user needs **Customize Application** permission (in addition to Modify All Data). Without it, SourceLoop can't deploy custom fields via the Metadata API. 2. Either grant the permission temporarily, reconnect with a System Administrator, or create the fields manually: - In Salesforce: **Setup -> Object Manager -> Lead -> Fields & Relationships -> New**. - Pick **Text(255)** for source / medium / campaign / content / term, **Text(1000)** for landing page. - Field API name should match what SourceLoop expects (visible in SourceLoop's Field Mapping tab as the "external_field"). 3. Repeat for the **Contact** object so attribution survives Lead conversion. ## Step 7: For sandbox / production confusion If you connected the wrong environment: 1. Open the Salesforce card and click **Disconnect** in the three-dot menu. 2. Toggle the **Sandbox** setting to the correct state. 3. Click **Connect**. Sandbox connections and production connections are separate records in SourceLoop. Data synced from one isn't shared with the other. Most teams keep both connections active, sandbox for testing the mapping, production for real data. ## Step 8: How to disconnect If you need to fully remove the Salesforce integration: 1. Open the Salesforce card in SourceLoop. 2. Click the three-dot menu and select **Disconnect**. 3. Confirm. SourceLoop stops syncing immediately and revokes the OAuth token from its end. Your SourceLoop data stays intact, only future Salesforce syncs are paused. To fully remove SourceLoop on Salesforce's side too (recommended for cleanup): 1. Go to **Salesforce Setup -> Connected Apps OAuth Usage**. 2. Find SourceLoop in the list. 3. Click **Revoke**. This invalidates any cached tokens on Salesforce's end. ## When to email support If you've worked through the checklist and the sync is still broken, email **hello@sourceloop.ai** with: - The Salesforce card's current status - The most recent error from the sync log (verbatim) - Your Salesforce instance URL (visible on the Salesforce card) - Whether you're connected to sandbox or production - Approximate time the issue started We'll dig in and respond within one business day. ## Frequently Asked Questions ### My Salesforce card shows 'Token expired'. What now? Click **Reconnect** and run OAuth again. Salesforce access tokens are 2 hours long; SourceLoop refreshes them automatically via the refresh token. A 'Token expired' status usually means the refresh token itself was revoked (someone removed SourceLoop from Salesforce's Connected Apps, or a session policy rotated tokens). Reconnect by the same Salesforce user fixes it. ### I connected to sandbox but want to switch to production. How? Disconnect the sandbox connection (three-dot menu -> Disconnect), toggle the Sandbox setting OFF on the Salesforce card, click Connect. OAuth now goes through login.salesforce.com (production). Your sandbox data in SourceLoop is preserved; the production data syncs into a separate connection record. ### A specific Lead isn't syncing. What should I check? Three causes. (1) The Lead's Email is missing or different from what SourceLoop captured. (2) The Lead was created before the connection and hasn't been modified since the initial sync window. Run **Initial sync** from the three-dot menu. (3) The connecting user doesn't have Read access to the Lead (sharing rules, role hierarchy, OWD). Test by viewing the Lead as that user. ### SourceLoop fields aren't writing to Salesforce, but contacts are syncing in. Why? Most common cause is Field-Level Security. The connecting Salesforce user's profile must have Edit access to the field, otherwise Salesforce silently rejects the write. Open Setup -> Profiles -> [connecting user's profile] -> Field-Level Security -> Lead, and grant Edit on every `sourceloop_*` field. Alternatively connect with a System Administrator user. ### The auto-create of sourceloop_* custom fields failed. What now? The connecting user needs **Modify All Data** plus the **Customize Application** permission (or System Administrator profile) to create custom fields via the API. If the user doesn't have it, either grant the permission, reconnect with a System Administrator user, or create the fields manually in Salesforce (Setup -> Object Manager -> Lead -> Fields & Relationships -> New). The field API names SourceLoop expects are listed in the Field Mapping tab. ### How do I force a full re-sync? Open the Salesforce card, click the three-dot menu, select **Run initial sync**. This ignores the delta cursor and pulls every Lead, Contact, Account, and Opportunity again. ### I'm getting 'INVALID_FIELD_FOR_INSERT_UPDATE' errors. What do they mean? SourceLoop tried to write to a field that doesn't exist or isn't writable. Usually means a field was renamed or deleted in Salesforce. Open the Field Mapping tab and remove the mapping for that field, or remap to a valid field. ### How do I completely disconnect? Open the Salesforce card, click the three-dot menu, select **Disconnect**. SourceLoop stops syncing immediately and revokes the OAuth token. Your SourceLoop data stays intact; no new Salesforce data syncs until you reconnect. To also remove SourceLoop from Salesforce's Connected Apps, go to Salesforce Setup -> Connected Apps OAuth Usage -> SourceLoop -> Revoke. --- # How to disconnect Salesforce from SourceLoop Disconnect SourceLoop from your Salesforce org. What stops, what is deleted, what is retained for 30 days, and how to request full removal under GDPR. Source: https://sourceloop.ai/help/disconnect-salesforce-from-sourceloop/ Updated: 2026-05-28 --- This article covers the Salesforce disconnect flow end-to-end: what happens immediately, what data we delete, what we retain for 30 days, and how to request full data removal. It exists so you (and your data-protection officer, if you have one) can answer every reasonable question about SourceLoop's handling of Salesforce-sourced data after disconnect. ## Before you start Set expectations correctly: - **Disconnecting on SourceLoop's side** revokes OAuth and stops sync. - **Revoking on Salesforce's side** removes SourceLoop from the org's Connected Apps OAuth Usage list (recommended for clean audit trail). - **Neither action deletes data from Salesforce itself**, SourceLoop has never had delete permissions on standard records, and disconnect doesn't trigger any cleanup on Salesforce's end. - **Existing SourceLoop data stays in your workspace** unless you explicitly request deletion via GDPR. If you have both **sandbox** and **production** connections, decide whether you're disconnecting one or both. They're stored as separate connection records and need to be disconnected independently. ## Step 1: Disconnect on SourceLoop's side 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> CRM -> Salesforce**. 3. On the Salesforce card, click the three-dot menu (top-right) and select **Disconnect**. 4. Confirm the disconnect dialog. The moment you confirm: - SourceLoop **revokes the OAuth access and refresh tokens** stored on our side. - SourceLoop **stops scheduling sync jobs** for this connection. - The Salesforce card on the CRM tab flips to **Disconnected**. - The cached field and pipeline schema is cleared. No additional Salesforce API calls are made by SourceLoop after this point. If you have **both sandbox and production connections**, repeat for each. ## Step 2: Revoke SourceLoop on Salesforce's side (recommended) This step is optional but recommended. It removes SourceLoop from Salesforce's Connected Apps OAuth Usage list, which: - Creates a record in the Salesforce audit log - Prevents any cached OAuth tokens from being reused - Is the cleanest end-state for SOC2 / ISO27001 / GDPR records 1. Sign in to Salesforce with a System Administrator user. 2. Open **Setup -> Apps -> Connected Apps -> Connected Apps OAuth Usage**. 3. Find **SourceLoop** in the list. 4. Click **Revoke** in the action column. 5. Confirm. Salesforce invalidates all tokens issued to SourceLoop from this user. Even if SourceLoop had any cached tokens, they'd be unusable from this moment. For sandbox, repeat in the sandbox org separately. ## Step 3: Decide what to do with the SourceLoop custom fields During normal operation, SourceLoop created these custom fields on the Lead and Contact objects: - `sourceloop_first_source__c`, `sourceloop_first_medium__c`, `sourceloop_first_campaign__c`, `sourceloop_first_content__c`, `sourceloop_first_term__c` - `sourceloop_first_landing_page__c`, `sourceloop_first_channel__c` - `sourceloop_latest_source__c`, `sourceloop_latest_medium__c`, `sourceloop_latest_campaign__c`, `sourceloop_latest_content__c`, `sourceloop_latest_term__c` - `sourceloop_latest_landing_page__c`, `sourceloop_latest_channel__c` - `sourceloop_id__c` After disconnect, these fields **stay in your Salesforce org** with their last-synced values. SourceLoop does not, and cannot, delete custom fields automatically (Salesforce requires the Customize Application permission and a manual confirmation flow for field deletion). Your options: - **Leave them** (recommended). They don't affect Salesforce behavior. If you reconnect later, SourceLoop picks them up automatically without recreating. - **Delete them manually**. In Salesforce, go to **Setup -> Object Manager -> Lead -> Fields & Relationships**, find each `sourceloop_*` field, and delete it. **Warning:** this also deletes the data stored on every Lead. Repeat for Contact. Once deleted, the historical attribution values are gone, even if you later reconnect. ## What SourceLoop retains after disconnect For transparency, exact retention: ### Deleted at the moment of disconnect - Salesforce OAuth access token (encrypted at rest) - Salesforce OAuth refresh token (encrypted at rest) - Salesforce instance URL cache - Scheduled sync jobs for the connection - Cached field and Describe metadata ### Retained for 30 days, then permanently deleted - The connection record (instance URL, sandbox/production flag, last-sync cursor, sync-in-progress flag) - Field mapping rules you configured - Lead Status mapping rules you configured - The most recent 30 days of sync logs The 30-day window lets you undo an accidental disconnect by reconnecting without losing your mapping configuration. After 30 days, the connection record and all associated configuration are purged from our database. ### Retained indefinitely (until you request deletion) - **Leads, Contacts, Accounts, Opportunities ingested from Salesforce inbound sync.** Once synced, these records belong to your SourceLoop workspace's data. They contain your business's customer information and are governed by your SourceLoop workspace's data policy, not the Salesforce connection. - **Conversion records, journeys, attribution.** SourceLoop-owned records that reference the Salesforce contact via email/external ID. - **Aggregated and anonymized analytics.** These retained records can be deleted on request, see "GDPR / full data removal" below. ## GDPR / full data removal To request complete removal of all data we ever ingested from your Salesforce org, including Leads, Contacts, Accounts, Opportunities, pipelines, sync logs, and derived analytics: 1. Email **hello@sourceloop.ai** with the subject "GDPR data deletion request" and your Salesforce instance URL (e.g., `acme.my.salesforce.com`). 2. Confirm your identity (authenticated SourceLoop account email or verified workspace owner). 3. We acknowledge within 2 business days. 4. We complete deletion within 30 days and confirm in writing. We delete: - The Salesforce connection record (if not auto-purged) - All Lead, Contact, Account, and Opportunity records ingested from the org - All conversion records and journeys tied to those records - All sync logs, error logs, configuration history - All custom analytics derived from Salesforce data This is irreversible. After completion, reconnecting Salesforce starts from a blank state. ## Reconnecting later If you change your mind within the 30-day grace window: 1. Sign in to SourceLoop. 2. Open **Setup -> CRM -> Salesforce**. 3. Click **Connect** on the Salesforce card. 4. Pick sandbox or production (matching your previous connection). 5. Run the OAuth flow. 6. SourceLoop resumes syncing from the last cursor, with your field and Lead Status mappings preserved. If you reconnect after 30 days, the connection record is fresh and mappings need to be reconfigured. Existing SourceLoop contacts get re-stitched on the next sync by email match. ## When to email support For anything outside the standard flow: - "I disconnected by mistake and want a full reset" → email hello@sourceloop.ai - "Our compliance team needs a written data-processing agreement (DPA)" → we provide a signed DPA on request - "An auditor needs evidence of what was retained vs deleted on our connection" → we provide a written audit trail keyed to your instance URL - "Salesforce still shows SourceLoop as connected even though I disconnected" → complete Step 2 above; if it still shows, email us with screenshots Email **hello@sourceloop.ai** for any of the above. We respond within one business day. ## Frequently Asked Questions ### How quickly does SourceLoop stop accessing my Salesforce org after I disconnect? Immediately. The OAuth access and refresh tokens are revoked on SourceLoop's side at the moment you click Disconnect. No further Salesforce API calls are made from your connection after that point. ### Does disconnecting delete any data from my Salesforce org? No. SourceLoop never deletes data from your Salesforce org, before, during, or after disconnect. We don't have delete permissions on standard records. Disconnecting only stops SourceLoop's read and write access. Everything that exists in Salesforce stays exactly as it was. ### What about the sourceloop_* custom fields that were auto-created in Salesforce? They stay in Salesforce after disconnect, with their last-synced values intact. SourceLoop cannot delete custom fields automatically (Salesforce requires Customize Application permission to drop fields). To remove them, an admin needs to manually delete each via Setup -> Object Manager -> Lead/Contact -> Fields & Relationships. Most teams leave them, the data is useful, and reconnecting later picks the fields up automatically. ### What data does SourceLoop retain after disconnect? The Salesforce-specific connection record (OAuth tokens, instance URL, field mappings, stage mappings, last-sync cursor) is kept for 30 days in case you reconnect, then permanently deleted. Records that originated from Salesforce inbound sync (Leads, Contacts, Accounts, Opportunities) stay in your SourceLoop workspace because they're now part of your data, not the connection. Your conversions, dashboards, and historical attribution are unaffected. ### How do I request full deletion of my Salesforce-sourced data from SourceLoop (GDPR)? Email hello@sourceloop.ai with the subject "GDPR data deletion request" and your Salesforce instance URL. We delete the connection record and all data ingested from that Salesforce org (Leads, Contacts, Accounts, Opportunities, pipelines, stages, sync logs) within 30 days. We confirm completion in writing. ### Sandbox and production connections, do I need to disconnect both? Yes if you want a complete disconnect. Sandbox and production are stored as separate connection records in SourceLoop. Disconnect each one independently from the corresponding card. ### Will disconnecting affect my SourceLoop subscription or other integrations? No. Disconnecting Salesforce is independent of your SourceLoop subscription and unrelated to any other CRM, payment, form, meeting, or chat integration you have configured. --- # How to track lead source in Pipedrive Push UTMs and lead source into Pipedrive Person, Organization, and Deal records. Native OAuth that handles regional API domains automatically. Source: https://sourceloop.ai/help/connect-pipedrive-to-sourceloop/ Updated: 2026-05-28 --- Pipedrive is the pipeline-first CRM most growing sales teams reach for. Its native fields cover the pipeline state well; what they don't cover is which marketing channel produced each Person. SourceLoop adds: first-touch and last-touch source on every Person, the full UTM trail, the visitor's pre-conversion journey, and multi-touch attribution models that follow Persons through to closed-won Deals. This article covers the OAuth connect flow only. After connecting, see: - [Push UTMs and lead source to Pipedrive Person fields](/help/push-utms-to-pipedrive/) - [Map SourceLoop stages to Pipedrive labels and statuses](/help/map-pipedrive-labels/) ## Why connect Pipedrive to SourceLoop? Pipedrive's native attribution is limited. SourceLoop adds: - **First-touch and last-touch source** on every Person, not a single Source field - **Full UTM trail** (source, medium, campaign, content, term) per touchpoint - **Multi-touch attribution models** (first, last, linear, position-based, time-decay) - **Visitor journey** viewable on the Person record (every page browsed pre-conversion) - **Revenue-by-channel** when payment integrations are connected, attributed back to the original Person All of this lives on **Pipedrive Person, Organization, and Deal custom fields**, so it shows up in Pipedrive list views, filters, and the Insights builder. ## Before you start You'll need: - A **SourceLoop workspace** with the [tracking pixel installed](/help/install-the-tracking-pixel/) - A **Pipedrive company account** (any plan) - A Pipedrive **Admin user** (required for OAuth + creating custom fields) - **Admin** or **Owner** role in SourceLoop ## Step 1: Open the CRM integrations page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Setup** in the left sidebar. ![SourceLoop Home page with the Setup entry in the left sidebar highlighted](/help/screenshots/sourceloop-setup-navigation.webp) 3. Click the **CRM** tab inside Setup. ![SourceLoop Setup CRM page showing the supported CRM provider cards including HubSpot, Salesforce, and Pipedrive](/help/screenshots/sourceloop-crm-integration-page.webp) 4. Scroll to the **Pipedrive** card and click **Connect**. ![SourceLoop CRM drawer with the Pipedrive Connect button highlighted](/help/screenshots/sourceloop-connect-pipedrive.webp) You'll be redirected to Pipedrive's OAuth consent screen. ## Step 2: Authorise SourceLoop on Pipedrive 1. Sign in to Pipedrive if you aren't already. 2. Pick the Pipedrive company you want to connect (if you have multiple). 3. Review the scopes SourceLoop is requesting (read/write on Persons, Organizations, Deals, Pipelines, Stages, Users, plus custom field management). 4. Click **Allow and install**. Pipedrive redirects you back to SourceLoop. The connection card flips to **Connected** and shows your Pipedrive company domain (e.g., `acme.pipedrive.com`). > **Pipedrive tokens auto-refresh** > Pipedrive OAuth access tokens are 1-hour-lived. SourceLoop stores the refresh token and rotates the access token automatically. You won't need to reconnect unless you revoke SourceLoop from Pipedrive's Marketplace apps page. ## Step 3: Pick what to sync After connecting, the Pipedrive drawer opens with sync settings: 1. **Inbound sync** (Pipedrive → SourceLoop): pulls your existing Persons, Organizations, and Deals into SourceLoop so SourceLoop can stitch attribution onto them and dashboards reflect your real pipeline. 2. **Outbound sync** (SourceLoop → Pipedrive): pushes UTM and source data from SourceLoop into Pipedrive custom fields on Person, Organization, and Deal records. 3. **Sync scope** — choose between **Contacts and Deals** (most teams) or **Deals only** (if you don't want SourceLoop touching the Person object). Most teams enable both directions and the full **Contacts and Deals** scope. Click **Save** to start the first sync. ## Step 4: Wait for the initial sync The initial sync pulls existing Pipedrive records into SourceLoop. Duration depends on volume: - Small account (under 5k Persons): under 5 minutes - Mid-size (5k-50k): 10-30 minutes - Large (50k+): up to a couple of hours The sync runs server-side. Watch the **Last sync** timestamp on the Pipedrive card. After the initial run, deltas sync every 15 minutes automatically. ## What gets written to Pipedrive Out of the box, SourceLoop creates and writes to these custom fields on the **Person** object: - `sourceloop_first_source`, `sourceloop_first_medium`, `sourceloop_first_campaign`, etc. - `sourceloop_latest_source`, `sourceloop_latest_medium`, `sourceloop_latest_campaign`, etc. - `sourceloop_first_landing_page`, `sourceloop_latest_landing_page` - `sourceloop_id` — internal link (don't delete or edit) These custom fields are created on Pipedrive's side automatically the first time SourceLoop writes to them, no manual setup required (provided the connecting user is an Admin). For mapping to your own existing custom fields or to push to Organizations and Deals, see [Push UTMs and lead source to Pipedrive Person fields](/help/push-utms-to-pipedrive/). ## What's next - **Field mapping** — by default SourceLoop maps to its own `sourceloop_*` fields. To map to your existing fields or push to Organization / Deal custom fields, see [Push UTMs to Pipedrive](/help/push-utms-to-pipedrive/). - **Label / status mapping** — translate SourceLoop's lifecycle stages into Pipedrive label_ids and deal stages. See [Map SourceLoop stages to Pipedrive labels](/help/map-pipedrive-labels/). - **Troubleshooting** — if a sync looks stuck or fields aren't writing, see [Troubleshoot Pipedrive sync issues](/help/troubleshoot-pipedrive-sync/). ## Frequently Asked Questions ### Do I need Pipedrive Professional or higher? No. The OAuth integration works on every Pipedrive plan including Essential. Some advanced features like automation triggers based on the UTM fields require Pipedrive Advanced or higher, but the sync itself doesn't. ### Pipedrive separates Persons and Organizations. How does SourceLoop handle that? Both. SourceLoop pushes contact-level attribution to Persons and (optionally) company-level attribution to Organizations. Most teams enable both, leads come in as Persons, account-level rollups land on Organizations. ### Does this sync historic Persons or only new ones? Both. The initial sync pulls existing Persons (matched by email) and stamps SourceLoop attribution onto any new activity from those people. Forward Persons sync on the 15-minute delta cadence. ### What if my Pipedrive company is on a different region (e.g., EU)? SourceLoop handles regional API domains automatically. The OAuth response includes your company-specific Pipedrive domain (e.g., `acme.pipedrive.com` or `acme-eu.pipedrive.com`), which SourceLoop stores per connection. All API calls route through the correct regional endpoint. ### Can I sync multiple Pipedrive companies? One Pipedrive company per SourceLoop website. For multiple Pipedrive accounts, create a separate SourceLoop website for each and connect them independently. ### Does this work with the Pipedrive Marketplace? SourceLoop uses Pipedrive's standard OAuth flow, the same one Marketplace apps use. The connection is authorised through Pipedrive's official consent screen and can be revoked any time from Pipedrive's Settings -> Tools and apps -> Marketplace apps. --- # How to push UTM parameters to Pipedrive Map SourceLoop UTM and source fields to Pipedrive Person, Organization, and Deal custom fields. Default mapping and supported API field types. Source: https://sourceloop.ai/help/push-utms-to-pipedrive/ Updated: 2026-05-28 --- By default, SourceLoop ships with a mapping that writes UTM and source data to `sourceloop_*` custom fields on the Pipedrive Person object. For most teams that's enough. For teams that want to populate existing fields, push to Organization or Deal, or use specific Pipedrive field types, the Field Mapping tab supports it. ## Before you start You'll need: - [Pipedrive connected to SourceLoop](/help/connect-pipedrive-to-sourceloop/) - A Pipedrive **Admin user** (required to create or edit custom fields) - **Admin** or **Owner** role in SourceLoop ## The fields SourceLoop can push **Contact identity:** email, contact name, phone, company name, country, city, title, LinkedIn URL **Attribution:** - First-touch: source, medium, campaign, content, term, landing page, channel, keyword - Last-touch (converting session): same set **Lifecycle:** lead status (raw + mapped), lifecycle stage (raw + mapped), lead score, qualified flag **Revenue:** quote value (expected), sales value (realised) ## Default mapping When you connect Pipedrive and enable outbound sync, SourceLoop sets up these default mappings on the **Person** object: - `first_source` → `sourceloop_first_source` (Text) - `first_medium` → `sourceloop_first_medium` (Text) - `first_campaign` → `sourceloop_first_campaign` (Text) - `first_landing_page` → `sourceloop_first_landing_page` (Text) - `latest_source` → `sourceloop_latest_source` (Text) - `latest_medium` → `sourceloop_latest_medium` (Text) - `latest_campaign` → `sourceloop_latest_campaign` (Text) - `latest_landing_page` → `sourceloop_latest_landing_page` (Text) - `lead_status_raw` → `label_ids` (Person labels, used as lead status) These fields are auto-created in Pipedrive on first sync. ## Step 1: Open the Pipedrive drawer 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> CRM -> Pipedrive**. The drawer opens with the connection status, the **Pipedrive → Sourceloop** / **Sourceloop → Pipedrive** sync toggles, and stacked mapping sections: **Lead status mapping**, **Lifecycle stage mapping**, **Deal stages**, and **Field mappings**. ![SourceLoop Pipedrive drawer showing sync toggles, lead status mapping, lifecycle stage mapping, and deal stages sections with pipeline values listed below](/help/screenshots/sourceloop-pipedrive-field-mapping.webp) 3. Scroll down to the **Field mappings** section. It lists the entities (Persons, Organizations, Deals) each with an **Edit** button. 4. Click **Edit** next to the entity you want to map. ## Step 2: Add a custom mapping 1. Click **Add mapping**. 2. Pick the **entity type**: - **Person** (most common) - **Organization** (account-level rollup) - **Deal** (closed-won attribution) 3. Pick the **SourceLoop field** (left dropdown), e.g., `first_source`. 4. Pick the **Pipedrive field** (right dropdown). The picker shows the human-readable label; the actual hex API key is stored under the hood. 5. Pick the **direction**: - **Outbound** (SourceLoop → Pipedrive) — for fields SourceLoop owns. - **Inbound** (Pipedrive → SourceLoop) — for Pipedrive data you want imported (e.g., Deal value, Owner, Stage). - **Bidirectional** — both ways; most-recent value wins. 6. Click **Save**. New mapping takes effect on the next sync cycle (within 15 minutes). ## Step 3: Pick the right Pipedrive field type when creating new fields If you're creating a new Pipedrive custom field (instead of mapping to an existing one), use these field types for the best results: - **Text** for UTM values, channels, landing pages under 255 chars - **Large text** for full URLs or long campaign names over 255 chars - **Monetary** for `quote_value` or `sales_value` (Pipedrive shows currency formatting) - **Date** for `first_seen` / `last_seen` timestamps - **Single option** for mapped lifecycle stages (so it shows as a dropdown in Pipedrive) - **Multiple options** for tags or multi-value categorisations (rare for UTM data) To create a field, in Pipedrive: **Settings -> Company settings -> Data fields -> Person -> + Custom field**. ## Step 4: Verify the mapping is working 1. Wait one sync cycle (~15 minutes) after a new conversion happens. 2. Open the Person in Pipedrive. 3. Scroll to the custom fields section, the SourceLoop fields should show the values from the contact in SourceLoop's Contacts Hub. If a field is blank, see [Troubleshoot Pipedrive sync issues](/help/troubleshoot-pipedrive-sync/). ## Organization (company) and Deal mappings Most teams stop at Person-level mappings. Two cases where Organization and Deal mappings are useful: - **B2B with account-based reporting** — push first-touch source to the Organization record so Pipedrive's Insights group by source at the account level. - **Closed-won attribution** — push the original Person's first-touch source to the Deal record so Pipedrive deal-revenue reports can group by source. Both use the same mapping flow, just pick the entity type accordingly. ## Mapping to Pipedrive's standard Lead Source field Pipedrive's **Source channel** field on Leads (Pipedrive's leadbox feature) is a separate concept from custom-field mapping. SourceLoop doesn't write to it directly because it's part of the Leads inbox (a pre-Deal staging area). Instead, SourceLoop writes to Person-level custom fields, which give you the same data with more granularity. If you specifically need data on the Leads inbox, push via the outbound webhook and write into Pipedrive's Leads API on your side. ## What's next After fields are mapped, the next step is aligning lifecycle stages with Pipedrive's Person labels and Deal stages. See [Map SourceLoop stages to Pipedrive labels](/help/map-pipedrive-labels/). ## Frequently Asked Questions ### What Pipedrive field types should I use for UTM data? Use **Text** (single-line) for UTM values like source, medium, campaign, content, term. Use **Text** for landing page (up to 255 chars) or **Large text** (256+ chars) for full URLs that might be longer. Use **Monetary** if you map quote_value or sales_value. Use **Date** for first-seen and last-seen timestamps if you push those. ### Do I need to create the custom fields in Pipedrive first? No. SourceLoop auto-creates the default `sourceloop_*` custom fields on the Person, Organization, and Deal objects the first time it writes. The connecting Pipedrive user needs to be an Admin for the auto-create to work. To map to existing fields instead, add the mapping in the Field Mapping tab. ### Can I push UTMs to the Organization (company) record, not just the Person? Yes. Add a mapping with entity type 'organization' and pick an Organization custom field as the target. SourceLoop aggregates UTM values across all Persons on the Organization and writes the consensus value. ### What about Deal custom fields? Yes, supported. On Deal entity mappings, SourceLoop writes the source data from the Person who opened the Deal. Useful for closed-won attribution reporting in Pipedrive's Insights builder. ### How does Pipedrive's custom field API name look? Mine is just a long hash. Pipedrive uses a 40-character hex string as each custom field's API key (e.g., `5e3a4b...`). SourceLoop's Field Mapping picker shows the human-readable label next to it so you don't have to memorise the hash. When you save a mapping, SourceLoop stores the hash so renaming the field in Pipedrive doesn't break the mapping. ### Pipedrive limits the number of custom fields per object. Will SourceLoop hit it? Pipedrive's limit is 80 custom fields per object on most plans (300 on Power and Enterprise). SourceLoop's default mapping creates about 12 custom fields on Person. You're nowhere near the limit unless you've already maxed out Pipedrive with other integrations. --- # How to map Pipedrive labels and deal stages Translate SourceLoop's internal lifecycle stages into Pipedrive Person labels (label_ids) and align inbound deal pipelines, both directions. Source: https://sourceloop.ai/help/map-pipedrive-labels/ Updated: 2026-05-28 --- Pipedrive doesn't have a built-in lifecycle stage field like HubSpot. Its closest equivalent is **Person labels** (the `label_ids` field), coloured tags you assign to a Person to indicate state. SourceLoop maps its internal lifecycle stages to one or more of those labels, both ways. This article walks through the mapping for Person labels and notes how SourceLoop handles Deal stages (preserved as-is on inbound, no mapping needed). ## Before you start You'll need: - [Pipedrive connected to SourceLoop](/help/connect-pipedrive-to-sourceloop/) - **Admin** or **Owner** role in SourceLoop - A list of your Pipedrive Person labels (visible in Pipedrive at **Settings -> Company settings -> Person Labels**) ## How the label mapping works The mapping is **per-connection** and **bidirectional**: - **Inbound** (Pipedrive → SourceLoop): when a Person syncs in, SourceLoop reads its assigned labels and translates each one to the matching SourceLoop stage. If a Person has multiple labels, the highest-funnel-position label wins (e.g., 'Customer' beats 'Hot Lead'). - **Outbound** (SourceLoop → Pipedrive): when a SourceLoop contact's stage changes, the mapping translates to the Pipedrive label and adds it to the Person's `label_ids`. Labels in Pipedrive are stored as numeric IDs but displayed by name and colour. SourceLoop's Stage Mapping panel handles the ID lookup automatically; you map by name. ## Step 1: Open the Pipedrive drawer 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> CRM -> Pipedrive**. The drawer opens with the sync toggles at the top and two stacked sections relevant here: **Lead status mapping** (for Person labels) and **Lifecycle stage mapping**. **Deal stages** sits below, but Pipedrive deal pipelines flow in automatically with no mapping required. ![SourceLoop Pipedrive drawer showing Lead status mapping (empty, prompting Edit), Lifecycle stage mapping (with explanation), and Deal stages with the Default pipeline values listed](/help/screenshots/sourceloop-pipedrive-field-mapping.webp) 3. Each mapping section has its own **Edit** button. Click **Edit** next to **Lead status mapping** to map Pipedrive Person labels to SourceLoop stages. ## Step 2: Map each Pipedrive label to a SourceLoop stage A typical mapping for an org using common label categories: - `Hot Lead` → In Progress - `Warm Lead` → Contacted - `Cold` → New - `Customer` → Converted - `Disqualified` → Lost For each row, pick the matching SourceLoop stage. The mapping saves on selection. > **Add-only labels** > In the Stage Mapping settings, you can choose between "Replace labels" (SourceLoop sets the exact mapped label and removes others on stage change) or "Add only" (SourceLoop adds the mapped label, never removes existing labels). Most teams use "Add only" so labels added manually by reps stay intact. ## Step 3: Verify the mapping is taking effect 1. Wait 15 minutes for the next delta sync (or click **Resync now** on the Pipedrive card). 2. Open a Person in SourceLoop's Contacts Hub. Its **Lifecycle stage** should reflect the mapped Pipedrive label. 3. Update the Person's labels in Pipedrive. Within 15 minutes the SourceLoop contact's stage updates to match. If the stage stays blank, see [Troubleshoot Pipedrive sync issues](/help/troubleshoot-pipedrive-sync/). ## Custom SourceLoop stages If the five default SourceLoop stages (New, Contacted, In Progress, Converted, Lost) don't match your funnel, add custom ones: 1. In SourceLoop, open **Settings -> Lifecycle stages**. 2. Click **Add stage** (e.g., 'Demo Scheduled', 'Proposal Sent', 'Negotiation'). 3. Save. Then map a Pipedrive label to each new stage in the Stage Mapping tab. ## Deal stages (no mapping needed) Pipedrive Deals run on multi-pipeline flows where each pipeline has its own ordered stages (e.g., Sales pipeline: Lead → Qualified → Proposal → Negotiation → Won). SourceLoop pulls your full pipeline structure on inbound sync and preserves it as-is for reporting. What this means in practice: - **No deal stage mapping is required.** Your existing pipeline structure shows up in SourceLoop's funnel and dashboard reports exactly as it is in Pipedrive. - **Multiple pipelines** are supported. SourceLoop preserves each pipeline separately, so a Deal in your Sales pipeline doesn't get conflated with one in your Onboarding pipeline. - **Stage rename / reorder in Pipedrive** flows through to SourceLoop on the next sync. If you want SourceLoop to write back to Deal stage (e.g., move a Deal forward based on a payment received), use a custom field mapping on the Deal entity instead, see [Push UTMs to Pipedrive](/help/push-utms-to-pipedrive/). ## Lead Inbox vs Persons (a quick note) Pipedrive's **Lead Inbox** is a pre-Deal staging area where new inbound leads land before promotion to a Deal. SourceLoop syncs Persons (which represent the contact regardless of stage), not the Lead Inbox specifically. If your team uses the Lead Inbox heavily, the mapping above still applies, the Person record is the source of truth. ## What's next If the mapping isn't behaving as expected, see [Troubleshoot Pipedrive sync issues](/help/troubleshoot-pipedrive-sync/). ## Frequently Asked Questions ### Pipedrive doesn't have a 'Lead Status' field. What does SourceLoop map to? Pipedrive uses **Person labels** (`label_ids`) as the closest equivalent to a lead status. Labels are coloured tags assigned to a Person (e.g., 'Hot Lead', 'Cold', 'Customer'). SourceLoop maps its internal lifecycle stages to one or more Pipedrive labels per stage. ### Can I use Pipedrive labels for multi-state tracking? Yes, a Person can have multiple labels at once. SourceLoop adds the mapped label when a stage changes; it doesn't remove other labels you've added manually. So labels like 'VIP' or 'Slack-customer' that your team uses outside the SourceLoop mapping stay intact. ### What about Deal stages? Do those need to be mapped? No. Pipedrive Deals use a multi-pipeline stage flow that SourceLoop pulls in as-is on inbound sync. Your existing pipeline structure is preserved exactly. The Stage Mapping tab is for **Person labels**, not Deal stages. ### My Pipedrive admin added a new label. Will SourceLoop pick it up? Yes, on the next properties cache refresh (every 24 hours, or on a manual reconnect). The new label appears in the Stage Mapping dropdown. Map it to a SourceLoop stage and save. ### What if a Person already has labels I don't want SourceLoop to touch? Configure SourceLoop's outbound to "add only, never remove" in the Stage Mapping settings. This way SourceLoop adds the mapped label on stage change but never removes labels you've added manually. ### Can I map multiple SourceLoop stages to the same Pipedrive label? Yes, that's the standard pattern. Pipedrive's labels are usually high-level ('Hot', 'Warm', 'Cold', 'Customer') while SourceLoop's stages can be more granular. Mapping 'In Progress' AND 'Converted' both to a 'Customer' label, for example, is fine. --- # How to troubleshoot Pipedrive sync issues with SourceLoop Checklist for diagnosing Pipedrive sync problems: OAuth errors, API domain mismatches, missing Persons, and custom field write failures. Source: https://sourceloop.ai/help/troubleshoot-pipedrive-sync/ Updated: 2026-05-28 --- Pipedrive sync issues fall into a handful of buckets. This checklist runs through them in order so you can get from "something's off" to a working sync in under fifteen minutes. ## Before you start Have these tabs open: - **SourceLoop's Pipedrive card** at **Setup -> CRM -> Pipedrive** (showing connection status + Last sync timestamp) - **Pipedrive's Marketplace apps page** at **Settings -> Marketplace apps** (to confirm SourceLoop is still installed) - The most recent **Sync log** entry on the Pipedrive card (three-dot menu) ## Step 1: Check the connection status 1. Open **Setup -> CRM -> Pipedrive** in SourceLoop. 2. Card status: - **Connected** + recent Last sync → healthy - **Connected** + stale Last sync (>1 hour) → stuck - **Token expired** → reconnect needed - **Disconnected** → run connect flow ## Step 2: For 'Token expired' / 'Disconnected' 1. Click **Reconnect**. 2. Sign in to Pipedrive. 3. Authorise SourceLoop's scopes. 4. You're returned to SourceLoop, card flips to **Connected**. If reconnect fails: - **"Not authorised"** → the user authorising isn't a Pipedrive Admin. Reconnect with an Admin user. - **"App removed"** → SourceLoop was uninstalled from Pipedrive Marketplace apps. The reconnect should re-install it. - **"Company changed"** → you signed in with a different Pipedrive company than the one previously connected. Sign in with the original company. ## Step 3: For a stuck sync 1. Open the Pipedrive card's three-dot menu and click **Sync log**. 2. Common errors and fixes: - **HTTP 401 (Unauthorized)** → token expired between cycles. Click **Reconnect**. - **HTTP 403 (Forbidden)** → the connecting user lost a permission. Reconnect with an Admin user. - **HTTP 429 (Rate limit exceeded)** → daily API quota hit. SourceLoop backs off automatically; the next day's quota refreshes. Mid-day fix: check Pipedrive Marketplace apps for other integrations consuming quota. - **`api_domain mismatch`** → Pipedrive moved your company to a different data centre. Click **Reconnect** to refetch the current API domain. 3. To force a sync now, click **Resync now** on the Pipedrive card. ## Step 4: For missing Persons If Persons aren't appearing in SourceLoop: 1. **Email mismatch.** SourceLoop matches by primary email. Check the Person in Pipedrive and confirm the primary email matches what SourceLoop captured. 2. **Pre-connection Persons.** Initial sync only pulls Persons modified after the connection. Run **Initial sync** from the three-dot menu to pull every Person. 3. **Visibility groups.** Pipedrive's visibility groups can restrict who sees a Person. The connecting user must have view access. Test by signing into Pipedrive as that user. ## Step 5: For 'fields not writing' (Persons sync in but custom fields stay blank) 1. **Outbound sync** is enabled on the Pipedrive card. 2. **Connecting user is Admin** for the auto-create flow to work. Otherwise the custom fields don't exist on the Pipedrive side and writes fail. 3. **Field Mapping** tab has at least one outbound mapping; direction is **outbound** or **bidirectional**. 4. **Field exists in Pipedrive**. Open **Settings -> Company settings -> Data fields -> Person** and search for the `sourceloop_*` fields. If they're missing, the auto-create failed; either reconnect with Admin or create them manually. 5. **Field type matches**. If you remapped to an existing Pipedrive custom field that's of the wrong type (e.g., Multiple Options instead of Text), Pipedrive may reject the write. Use Text for UTM values. ## Step 6: For auto-create of sourceloop_* fields failing 1. The connecting user must be a Pipedrive **Admin**. 2. Reconnect with an Admin user, or grant Admin to the current connecting user. 3. Alternatively, create the fields manually: - In Pipedrive: **Settings -> Company settings -> Data fields -> Person -> + Custom field**. - Pick **Text** as the type. - Name: match the API name SourceLoop expects (visible in SourceLoop's Field Mapping tab under "external_field"). 4. Repeat for Organization and Deal objects if you mapped to those entities. ## Step 7: For 'company moved to new data centre' If Pipedrive moved your company between data centres (e.g., US → EU), the API domain stored in SourceLoop becomes stale. 1. Click **Reconnect** on the Pipedrive card. 2. The OAuth response carries the new API domain (e.g., `acme-eu.pipedrive.com`), and SourceLoop updates the connection record. 3. Sync resumes within 15 minutes. ## Step 8: How to disconnect 1. Open the Pipedrive card. 2. Click the three-dot menu -> **Disconnect**. 3. Confirm. SourceLoop stops syncing immediately and revokes the OAuth token. Your SourceLoop data stays intact. To fully remove SourceLoop on Pipedrive's side: 1. **Pipedrive Settings -> Marketplace apps**. 2. Find SourceLoop. 3. Click **Uninstall**. This revokes any cached tokens on Pipedrive's end. ## When to email support If you've worked through the checklist and the sync is still broken, email **hello@sourceloop.ai** with: - The Pipedrive card's current status - The most recent error from the sync log (verbatim) - Your Pipedrive company domain (e.g., `acme.pipedrive.com`) - Approximate time the issue started We'll dig in and respond within one business day. ## Frequently Asked Questions ### My Pipedrive card shows 'Token expired'. What now? Click **Reconnect** and run OAuth again. Pipedrive access tokens are 1-hour-lived; SourceLoop rotates them automatically via the refresh token. A 'Token expired' status usually means the refresh token itself was revoked (the connection was removed from Pipedrive's Marketplace apps page, or your Pipedrive admin changed something on the OAuth app). Reconnect by the same Pipedrive admin fixes it. ### A specific Person isn't syncing. What should I check? Three causes. (1) Email mismatch, SourceLoop matches by email; if the Person's primary email differs from what SourceLoop captured, they won't link. (2) Person created before the connection and not modified since the initial sync. Run **Initial sync** from the three-dot menu. (3) Permission visibility, the connecting Pipedrive user must have view access to the Person (Pipedrive's visibility groups can restrict this). ### SourceLoop fields aren't writing to Pipedrive. Why? Most common cause is the auto-create of custom fields failed. The connecting Pipedrive user must be an **Admin** for SourceLoop to create custom fields via the API. If the user isn't Admin, either grant Admin temporarily and reconnect, or create the fields manually in Pipedrive and add the mapping in SourceLoop's Field Mapping tab. ### My Pipedrive company moved to the EU data centre. Will SourceLoop still work? Yes. The Pipedrive OAuth response includes your company-specific API domain (e.g., `acme-eu.pipedrive.com`), which SourceLoop stores per connection. When Pipedrive moves a company between data centres, the OAuth refresh response carries the new domain and SourceLoop picks it up automatically. If it doesn't, click **Reconnect** to force a re-fetch. ### I'm seeing 'API call quota exceeded'. What now? Pipedrive limits API calls per company per day. SourceLoop respects the limit and backs off automatically. If you're hitting it repeatedly, your Pipedrive account is likely also being hit by another integration (Zapier, custom scripts). The Pipedrive admin can check daily usage at Settings -> Marketplace apps. ### How do I force a full re-sync? Open the Pipedrive card, click the three-dot menu, select **Run initial sync**. This ignores the delta cursor and pulls every Person, Organization, Deal, and Pipeline again. ### How do I completely disconnect? Open the Pipedrive card, click the three-dot menu, select **Disconnect**. SourceLoop stops syncing immediately and revokes the OAuth token. Your SourceLoop data stays intact; no new Pipedrive data syncs until you reconnect. To also remove SourceLoop on Pipedrive's side, go to Pipedrive Settings -> Marketplace apps -> SourceLoop -> Uninstall. --- # How to disconnect Pipedrive from SourceLoop Disconnect SourceLoop from your Pipedrive company. What stops, what is deleted, what is retained for 30 days, and how to request full removal under GDPR. Source: https://sourceloop.ai/help/disconnect-pipedrive-from-sourceloop/ Updated: 2026-05-28 --- This article covers the Pipedrive disconnect flow end-to-end: what stops immediately, what data we delete, what we retain for 30 days, and how to request full data removal. It exists so you (and your data-protection officer, if you have one) can answer every reasonable question about SourceLoop's handling of Pipedrive-sourced data after disconnect. ## Before you start Set expectations: - **Disconnecting on SourceLoop's side** revokes OAuth and stops sync. - **Uninstalling on Pipedrive's side** removes SourceLoop from the company's Marketplace apps list (recommended for clean cleanup). - **Neither action deletes data from Pipedrive itself.** SourceLoop has never had delete permissions on Persons, Organizations, or Deals. Disconnect doesn't trigger any cleanup on Pipedrive's end. - **Existing SourceLoop data stays in your workspace** unless you explicitly request deletion under GDPR. ## Step 1: Disconnect on SourceLoop's side 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> CRM -> Pipedrive**. 3. On the Pipedrive card, click the three-dot menu (top-right) and select **Disconnect**. 4. Confirm the disconnect dialog. The moment you confirm: - SourceLoop **revokes the OAuth access and refresh tokens** stored on our side. - SourceLoop **stops scheduling sync jobs** for this connection. - The Pipedrive card on the CRM tab flips to **Disconnected**. - The cached custom field metadata and pipeline schema are cleared. No additional Pipedrive API calls are made by SourceLoop after this point. ## Step 2: Uninstall SourceLoop on Pipedrive's side (recommended) This step is optional but recommended for completeness: - Creates a record in Pipedrive's audit log - Prevents any cached OAuth tokens from being reused - Is the cleanest end-state for SOC2 / GDPR records 1. Sign in to Pipedrive as a Company Admin. 2. Open **Settings (your initials, top-right) -> Marketplace apps**. 3. Find **SourceLoop** in the Installed apps list. 4. Click **Uninstall**. 5. Confirm. Pipedrive invalidates the OAuth tokens issued to SourceLoop. From this point on, even cached tokens are unusable. ## Step 3: Decide what to do with the SourceLoop custom fields During normal operation, SourceLoop auto-created these custom fields on the Person object (and Organization, Deal): - `sourceloop_first_source`, `sourceloop_first_medium`, `sourceloop_first_campaign`, `sourceloop_first_content`, `sourceloop_first_term` - `sourceloop_first_landing_page`, `sourceloop_first_channel` - `sourceloop_latest_source`, `sourceloop_latest_medium`, `sourceloop_latest_campaign`, `sourceloop_latest_content`, `sourceloop_latest_term` - `sourceloop_latest_landing_page`, `sourceloop_latest_channel` - `sourceloop_id` (internal mapping ID) After disconnect, these fields **stay in your Pipedrive company** with their last-synced values. SourceLoop does not, and cannot, delete custom fields automatically. Options: - **Leave them** (recommended). They don't affect Pipedrive behavior. If you reconnect, SourceLoop picks them up automatically without recreating. - **Delete them manually**. In Pipedrive, go to **Settings -> Company settings -> Data fields -> Person** (and **Organization** / **Deal**), filter for `sourceloop_*`, and delete each. **Warning:** this also deletes the data stored on every Person. Once deleted, the historical attribution values are gone, even if you reconnect later. ## What SourceLoop retains after disconnect ### Deleted at the moment of disconnect - Pipedrive OAuth access token (encrypted at rest) - Pipedrive OAuth refresh token (encrypted at rest) - Company-specific API domain cache - Scheduled sync jobs for the connection - Cached field and pipeline metadata ### Retained for 30 days, then permanently deleted - The connection record (company domain, last-sync cursor, sync-in-progress flag) - Field mapping rules you configured - Label / lead status mapping rules you configured - The most recent 30 days of sync logs The 30-day window covers accidental disconnects, reconnect within that window and your mappings are preserved. After 30 days, the connection record and all associated configuration are purged. ### Retained indefinitely (until you request deletion) - **Persons, Organizations, and Deals ingested from Pipedrive inbound sync.** Once synced, these records are part of your SourceLoop workspace data and follow your workspace's data policy. - **Conversion records, journeys, attribution.** SourceLoop-owned records that reference the Pipedrive Person via email/external ID. - **Aggregated and anonymized analytics.** These retained records can be deleted on request, see below. ## GDPR / full data removal To request complete removal of all data we ever ingested from your Pipedrive company, including Persons, Organizations, Deals, pipelines, sync logs, and derived analytics: 1. Email **hello@sourceloop.ai** with the subject "GDPR data deletion request" and your Pipedrive company domain (e.g., `acme.pipedrive.com`). 2. Confirm your identity (authenticated SourceLoop account email or verified workspace owner). 3. We acknowledge within 2 business days. 4. We complete deletion within 30 days and confirm in writing. We delete: - The Pipedrive connection record (if not auto-purged) - All Person, Organization, and Deal records ingested from the company - All conversion records and journeys tied to those records - All sync logs, error logs, configuration history - All custom analytics derived from Pipedrive data This is irreversible. After completion, reconnecting Pipedrive starts from a blank state. ## Reconnecting later If you change your mind within the 30-day grace window: 1. Sign in to SourceLoop. 2. Open **Setup -> CRM -> Pipedrive**. 3. Click **Connect** on the Pipedrive card. 4. Run the OAuth flow with Pipedrive. 5. SourceLoop resumes syncing from the last cursor, with your field and label mappings preserved. If you reconnect after 30 days, the connection record is fresh. Mappings need to be reconfigured. Existing SourceLoop contacts get re-stitched to Pipedrive Persons on the next sync by email match. ## When to email support For anything outside the standard flow: - "I disconnected by mistake and want a full reset" → email hello@sourceloop.ai - "Our compliance team needs a written data-processing agreement (DPA)" → we provide a signed DPA on request - "An auditor needs evidence of what was retained vs deleted on our connection" → we provide a written audit trail keyed to your Pipedrive domain - "Pipedrive Marketplace still shows SourceLoop installed even though I disconnected" → complete Step 2 above; email if it still shows Email **hello@sourceloop.ai** for any of the above. We respond within one business day. ## Frequently Asked Questions ### How quickly does SourceLoop stop accessing my Pipedrive company after I disconnect? Immediately. The OAuth access and refresh tokens are revoked on SourceLoop's side at the moment you click Disconnect. No further Pipedrive API calls are made from your connection after that point. ### Does disconnecting delete data from my Pipedrive company? No. SourceLoop never deletes data from your Pipedrive company, before, during, or after disconnect. Pipedrive Persons, Organizations, and Deals stay exactly as they were. We don't request delete-level scopes during OAuth. ### What about the sourceloop_* custom fields auto-created in Pipedrive? They stay in Pipedrive after disconnect, with their last-synced values intact. SourceLoop cannot delete custom fields automatically. To remove them, a Pipedrive admin needs to manually delete each at Settings -> Company settings -> Data fields. Most teams leave them, the data is useful, and reconnecting picks them up automatically. ### What data does SourceLoop retain after disconnect? The Pipedrive-specific connection record (OAuth tokens, company API domain, field mappings, label mappings, last-sync cursor) is kept for 30 days in case you reconnect, then permanently deleted. Records ingested from Pipedrive inbound sync (Persons, Organizations, Deals, pipelines, stages) stay in your SourceLoop workspace because they're part of your data, not the connection. Your conversions, dashboards, and historical attribution are unaffected. ### How do I request full deletion of my Pipedrive-sourced data from SourceLoop (GDPR)? Email hello@sourceloop.ai with the subject "GDPR data deletion request" and your Pipedrive company domain (e.g., acme.pipedrive.com). We delete the connection record and all data ingested from that Pipedrive company (Persons, Organizations, Deals, pipelines, sync logs) within 30 days, and confirm in writing. ### My Pipedrive admin sees SourceLoop still installed even after I disconnected. Why? SourceLoop's side disconnect revokes tokens but doesn't auto-uninstall the app from Pipedrive's Marketplace apps page. For a complete cleanup, also uninstall from Pipedrive Settings -> Marketplace apps -> SourceLoop -> Uninstall. ### Will disconnecting affect my SourceLoop subscription or other integrations? No. Disconnecting Pipedrive is independent of your SourceLoop subscription. Other CRM, payment, form, meeting, and chat integrations are unaffected and continue running normally. --- # Section: Ads Connect Google, Meta, TikTok, LinkedIn, and Microsoft Ads to sync offline conversions, push enhanced-conversion data, and pull spend into reports. # Google Ads Conversion Tracking & Attribution Setup Guide OAuth Google Ads to push offline conversions back to your campaigns. Enhanced Conversions, GCLID upload, and MCC support from one connection. Source: https://sourceloop.ai/help/connect-google-ads/ Updated: 2026-05-28 --- Connecting Google Ads to SourceLoop opens up the full offline-conversion loop. Every time a tracked lead converts (form submission, meeting booked, payment received), SourceLoop pushes that conversion back to Google Ads against the matching GCLID. The bidding algorithms learn from real revenue instead of just clicks, and your CPA and ROAS reports finally reflect what actually happened. This article covers the OAuth connect flow only. After connecting, see: - [Configure Google Ads offline conversion sync](/help/configure-google-ads-offline-conversions/) - [Troubleshoot Google Ads sync issues](/help/troubleshoot-google-ads-sync/) ## Why connect Google Ads to SourceLoop? Google Ads optimises bids based on the conversion events you report. By default, Google sees the on-site events that fire on tag-managed pages (form submits, page views), but it doesn't see what happens after the form, the demo that converted to a paid plan, the trial that became a $10K/month contract. SourceLoop closes that gap: - **Push offline conversions** to your Google Ads conversion actions via the Google Ads API - **Enhanced Conversions** (hashed email / phone) automatically populated so Google can match conversions to clicks even when GCLID is missing - **Revenue values** sent on every conversion when payment integrations are connected - **Bid optimisation** trains on real qualified leads and paying customers, not vanity events - **Multi-touch attribution** within SourceLoop, while still feeding Google the conversion data they need for tCPA and tROAS bidding ## Before you start You'll need: - A **SourceLoop workspace** with the [tracking pixel installed](/help/install-the-tracking-pixel/) - A **Google Ads account** (any tier; Google Ads doesn't charge for the API) - A Google user with **Standard access** or higher on the ad account you want to connect - **Admin** or **Owner** role in SourceLoop (Editors can't add integrations) ## Step 1: Open the Ad Platforms page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Setup** in the left sidebar. 3. Click the **Ad Platforms** tab. 4. Scroll to the **Google Ads** card and click **Connect**. You'll be redirected to Google's OAuth consent screen. ## Step 2: Authorise SourceLoop on Google 1. Sign in to Google if you aren't already. 2. Pick the Google account that has access to the Google Ads account you want to connect. 3. Review the requested scope (read/write access to the Google Ads API). 4. Click **Continue**. Google redirects you back to SourceLoop with an authorisation code, which SourceLoop exchanges for an access token + refresh token. The tokens are encrypted at rest. ## Step 3: Pick the Google Ads account If your Google user has access to more than one Google Ads account (common for agencies and teams using a Manager / MCC account), SourceLoop shows a picker with every account accessible to that user. 1. Find the Google Ads account you want to connect. 2. Click **Connect this account**. SourceLoop creates a connection record for that specific Google Ads account. The connection card on the Ad Platforms tab flips to **Connected** with the account name and Customer ID. > **To connect a second account, repeat the flow** > Each Google Ads account is its own connection. If you manage multiple accounts under a Manager (MCC), run the Connect Google Ads flow once per ad account. They sync and push conversions independently. ## Step 4: Wait for the first sync After connecting, SourceLoop kicks off: 1. **Hierarchy sync** — pulls your campaigns, ad groups, and ads into SourceLoop so spend and impressions can be reported alongside conversions 2. **90-day click mapping backfill** — pulls historical GCLID to ad mappings so SourceLoop knows which ad each historical click came from 3. **Connection status** flips to **Active** once the initial sync completes The first sync takes anywhere from a few minutes (small accounts) to ~30 minutes for accounts with 90 days of high-traffic history. The conversion push starts immediately once the connection is Active, you don't have to wait for hierarchy sync to finish. ## What gets synced Once connected, SourceLoop runs two flows automatically: **PULL (Insights sync, daily at 05:00 UTC):** - Campaign / ad group / ad hierarchy - Daily spend, impressions, clicks per campaign / ad group / ad - 14-day rolling re-sync to catch Google's late-attribution updates - Refreshed in SourceLoop dashboards within minutes of each sync run **PUSH (Conversions API, every 2 minutes):** - Every SourceLoop conversion (form submit, meeting, chat, payment) where the visitor's session has a `gclid` cookie or a hashed email / phone match - Conversion value (currency) and currency code if a value is configured - Enhanced Conversions data (hashed email / phone) for click-matching when GCLID is absent - Adjustments (RESTATE / RETRACT) on subscription upgrades and refunds when payments are connected ## What's next - **Pick which SourceLoop events get pushed to which Google Ads conversion action.** See [Configure Google Ads offline conversion sync](/help/configure-google-ads-offline-conversions/). - **Troubleshoot** any push errors or stale syncs: [Troubleshoot Google Ads sync issues](/help/troubleshoot-google-ads-sync/). - **Disconnect or reset** the integration: [Disconnect Google Ads from SourceLoop](/help/disconnect-google-ads-from-sourceloop/). ## Frequently Asked Questions ### Do I need a Google Ads Developer Token to connect? No. SourceLoop holds the Developer Token on our side, so you don't need to apply for one yourself. You authorise the OAuth flow with your Google account and we handle the rest. The Developer Token tier is Basic which covers every real-world ad account size. ### Does SourceLoop work with a Google Ads Manager (MCC) account? Yes, but you connect each sub-account individually. MCC accounts can't directly push offline conversions, only the individual ad accounts under them can. After OAuth, SourceLoop shows the list of accounts accessible via the connected Google user and you pick which one to wire up. Repeat for additional accounts. ### What scopes does SourceLoop request from Google? The `adwords` scope (read/write access to the Google Ads API) for the specific accounts you select during the account picker step. SourceLoop does not request access to Gmail, Drive, or any other Google service. ### Do I need Enhanced Conversions enabled in Google Ads first? No. SourceLoop enables Enhanced Conversions automatically on the conversion actions you select. The first push to a conversion action turns on the Enhanced Conversions flag for User-provided data so hashed email and phone can be matched to ad clicks even when GCLID isn't available. ### How long do Google Ads access tokens last? 1 hour. SourceLoop stores the refresh token and rotates the access token automatically as needed, so you won't have to reconnect unless you revoke the integration on Google's side. ### Can I connect multiple Google Ads accounts to one SourceLoop workspace? Yes. Each Google Ads account is a separate connection in SourceLoop. Run the OAuth flow once, pick the first account; run it again with the same Google user, pick the second account. They sync and push conversions independently. ### Does this work for Performance Max and Demand Gen campaigns? Yes. Offline conversion uploads are campaign-type-agnostic. As long as the conversion action exists in your Google Ads account, SourceLoop can push conversions against it, whether the original click came from Search, Display, Video, Performance Max, Demand Gen, or any other type. --- # How to Sync Revenue and Offline Conversions in Google Ads Pick which SourceLoop events push to which Google Ads conversion action, configure value mapping, dedup window, and Enhanced Conversions. Source: https://sourceloop.ai/help/configure-google-ads-offline-conversions/ Updated: 2026-05-28 --- After you've [connected Google Ads to SourceLoop](/help/connect-google-ads/), the next step is wiring up the actual conversion mappings. Which SourceLoop event (form submit, meeting booked, payment) pushes to which Google Ads conversion action, with what value, attribution model, and dedup behavior. This is the part that determines whether Google's bidding optimises toward real revenue or toward whatever happens to fire first on the page. ## Before you start You'll need: - [Google Ads connected](/help/connect-google-ads/) (the card on the Ad Platforms tab shows **Active**) - A **Google Ads conversion action** already created in Google Ads. If you don't have one yet, in Google Ads go to **Tools -> Conversions -> + New conversion action -> Import -> Other data sources -> Track conversions from clicks**. - The numeric **Conversion Action ID** from Google Ads (find it in the URL of the conversion action page, or via Tools -> Conversions, click into the action, copy the ID from the URL) - **Admin** or **Owner** role in SourceLoop ## Step 1: Open the Google Ads drawer in SourceLoop 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> Google Ads**. 3. The Google Ads drawer opens with the connection status at the top and a **Conversion sync** section below. ## Step 2: Add a conversion mapping 1. Click **Add conversion mapping** (or **+ New** depending on the UI variant). 2. Pick the **Trigger event type** from the dropdown: - **Conversion created** — fires for every new lead conversion (form submit, meeting, chat with email captured, initial payment) - **Expected revenue updated** — fires when the lead's quote_value field changes (CRM-driven) - **Realized revenue updated** — fires when a payment integration confirms revenue (Stripe charge, Lemon Squeezy order, Paddle transaction) 3. (Optional) Set a **Trigger event name filter** — leave blank to match all events of that type, or filter by source (e.g., `form_submission`, `calendly_booking`, `stripe_charge.succeeded`). 4. Pick an **Attribution model**: - **Last touch** — the converting session's source - **First touch** — the original acquisition source 5. Paste the **Conversion Action ID** from Google Ads (just the numeric ID, e.g., `12345678901`). ## Step 3: Configure value and dedup settings 1. Toggle **Include value** if you want to send a revenue figure with each conversion. 2. Pick the **value source**: - `quote_value` — expected revenue (from CRM or lead scoring) - `sales_value` — actual realised revenue (from payment integrations) - **Fixed value** — a flat amount per conversion (e.g., $50 per lead) 3. Set **Currency** if you want to override the workspace default (Google Ads uses this for tROAS). 4. Set the **Dedup window** in minutes (default 1440 = 24 hours). Within this window, repeated conversions of the same type from the same GCLID won't be re-pushed. 5. Click **Save**. The mapping appears in the Conversion sync list. SourceLoop starts pushing matching conversions to Google Ads on the next 2-minute flush cycle. ## Step 4: Verify conversions are arriving in Google Ads 1. Wait 6 to 24 hours after the first matching conversion fires in SourceLoop (Google Ads has a delay before the count appears). 2. In Google Ads, open **Tools -> Conversions** and click into the conversion action you mapped. 3. The **Recent conversions** column should show counts climbing. 4. For more detail, check **Tools -> Conversions -> Conversion sources -> Import**. Each batch SourceLoop pushes is recorded there with timestamps and any errors. In SourceLoop, you can also check **Setup -> Ad Platforms -> Google Ads -> Sync log** to see every conversion push with the GCLID, value, status, and Google Ads response. > **Pending verification status is normal** > New Google Ads conversion actions show "Pending verification" until Google has seen real conversions arriving. After SourceLoop pushes a few, the status flips to Active automatically. ## Enhanced Conversions For every push, SourceLoop also sends hashed user data (email + phone, SHA256) as Enhanced Conversions. This gives Google a fallback match path when the GCLID isn't available (e.g., the visitor's cookies expired or they used a different browser). Enhanced Conversions are enabled automatically on the conversion action the first time SourceLoop pushes to it. No additional setup is needed on Google Ads. ## What gets pushed For every matching SourceLoop event, the push includes: - **GCLID** captured by the tracking pixel from the visitor's first session (when present) - **Conversion timestamp** in RFC 3339 format with timezone - **Conversion action resource name** (built from the Customer ID + Action ID) - **Conversion value** (currency units) when `include_value=true` - **Currency code** (ISO 4217) - **Enhanced Conversions PII** (hashed email, hashed phone) for fallback matching - **Order ID** (the SourceLoop conversion's external_id) for dedup For subscription upgrades, downgrades, and refunds (when payments are connected), SourceLoop pushes **conversion adjustments** (RESTATE / RETRACT) against the original conversion. This keeps your Google Ads revenue reports accurate even when the deal value changes after close. ## What's next - **Troubleshoot pushes that aren't appearing in Google Ads:** [Troubleshoot Google Ads sync issues](/help/troubleshoot-google-ads-sync/). - **Add more conversion actions** for separate funnels (lead, demo, paid plan, upgrade): repeat steps 2-3 with each Google Ads Conversion Action ID. ## Frequently Asked Questions ### What's the difference between a 'conversion source' and a 'conversion action' in Google Ads? A conversion source is the system that reports the conversion (e.g., website, app, import). A conversion action is the specific event being tracked (e.g., Form Submission, Demo Booked, Purchase). SourceLoop pushes conversions via the Import source against specific conversion action IDs. ### Do I need to create the Google Ads conversion action first? Yes. Open Google Ads, go to Tools -> Conversions -> + New conversion action, pick Import -> Other data sources -> Track conversions from clicks. Name it something specific (e.g., "SourceLoop - Demo Booked"). Then paste its Conversion Action ID into SourceLoop. ### Where do I find the Conversion Action ID? In Google Ads, open the conversion action and check the URL. The numeric ID at the end (e.g., `conversionActionId=12345678`) is what you paste into SourceLoop. Alternatively, run a GAQL query against `conversion_action` and grab `conversion_action.id`. ### Can I send revenue values with conversions? Yes. In the SourceLoop configuration, toggle "Include value" and pick the value source (e.g., `quote_value`, `sales_value`, or a fixed value). Currency is read from the same source if available, otherwise it defaults to your workspace currency. Google Ads uses the value for tROAS bidding. ### What's the dedup window for? Google Ads deduplicates conversions for the same GCLID within a window. SourceLoop sets a 24-hour default but you can change it. Within the window, repeated conversions of the same type from the same click are ignored. This matches Google's default behavior for tag-fired conversions. ### My conversion action shows as 'Pending verification' in Google Ads. What now? That's normal for new conversion actions. Google Ads needs to see real conversions arriving before it marks the action as Active. After a few pushed conversions reach Google Ads (within 6 hours of the first push), the status flips automatically. ### Can I push the same SourceLoop event to multiple Google Ads conversion actions? Yes. Add separate configuration rows in SourceLoop, one per target Google Ads action. Useful for splitting Search-only vs Display-only conversion actions, or for sending both a Lead conversion and a Purchase conversion off the same form. --- # How to troubleshoot Google Ads sync issues Checklist for diagnosing Google Ads connection and conversion-push issues: OAuth errors, GCLID mismatches, missing conversions, MCC permissions. Source: https://sourceloop.ai/help/troubleshoot-google-ads-sync/ Updated: 2026-05-28 --- Google Ads sync issues fall into a handful of buckets. This checklist runs through them in order so you can get from "something's off" to a working sync in fifteen minutes. ## Before you start Have these tabs open: - **SourceLoop's Google Ads card** at **Setup -> Ad Platforms -> Google Ads** - **Google Ads** at **Tools -> Conversions** (for verifying conversion actions) - **Google Ads** at **Tools -> API Center** (for checking API quota usage) - The most recent **Sync log** on the Google Ads card (three-dot menu) ## Step 1: Check the connection status 1. Open **Setup -> Ad Platforms -> Google Ads** in SourceLoop. 2. Look at the card: - **Active** with recent **Last sync** → healthy - **Active** with stale **Last sync** (>24 hours) → Insights sync stuck - **Token expired** → reconnect needed - **Disconnected** → run the Connect flow ## Step 2: For 'Token expired' / 'Disconnected' 1. Click **Reconnect**. 2. Sign in to Google with a user that has Standard access or higher on the Google Ads account. 3. Authorise the `adwords` scope. 4. Pick the same Google Ads account in the picker. 5. The card flips to **Active**. If reconnect fails immediately: - **"User not authorized"** → the Google user doesn't have Standard+ access on the Google Ads account. Switch to a user that does. - **"Invalid customer ID"** → rare; happens if the Google Ads account was renamed or transferred. Email hello@sourceloop.ai with the Customer ID. ## Step 3: For 'conversions not showing in Google Ads' This is the most common case. Walk through these checks: 1. **Open the Sync log** on the Google Ads card (three-dot menu). 2. Find the conversion you expected. Each push is logged with the GCLID, conversion action, value, and status: - **sent** → SourceLoop successfully pushed the conversion to Google Ads. Allow up to 6 hours for it to appear in Google Ads UI. - **pending_identity** → the visitor had no GCLID and no email/phone to fall back on. Either Enhanced Conversions wasn't enabled, or the visitor really has no identifiable signal. Skip these. - **error** → Google Ads rejected the push. Click into the row to see the exact error from Google. Common errors and fixes: - **`CONVERSION_ACTION_NOT_FOUND`** → wrong Conversion Action ID in the config. See FAQ above for how to find the right one. - **`UNSUPPORTED_DECIMAL_VALUE`** → conversion value has too many decimal places. Cap at 4 decimals; SourceLoop usually handles this, but bespoke value sources can produce floats. - **`CONVERSION_PRECEDES_CLICK`** → conversion timestamp is before the GCLID's click timestamp. Usually a clock-skew issue; should resolve on the next push. - **`CONVERSION_TIME_TOO_LATE`** → conversion is outside Google's 90-day lookback. Nothing to do; the conversion is too old to attribute. ## Step 4: For 'GCLID missing on the conversion' If the Sync log shows `pending_identity` for most conversions, the GCLID isn't being captured by your tracking pixel. Two checks: 1. **Pixel placement.** Confirm the SourceLoop tracking script is installed on every page that receives Google Ads traffic, especially landing pages. The tracker needs to be on the *landing* page (where the GCLID first arrives), not just the checkout page. 2. **GCLID URL param.** Open a real Google Ads campaign URL, click an ad, and check the destination URL. There should be a `gclid=` parameter. If not, your Google Ads account's "Auto-tagging" is off; turn it on at Google Ads -> Admin -> Account settings -> Auto-tagging. Once the pixel sees GCLIDs, future conversions get pushed with them. Past conversions can't be retroactively GCLID-stamped, but Enhanced Conversions (hashed email / phone) can still match many of them. ## Step 5: For 'developer token approval required' This means your Google Ads account's API operations exceeded SourceLoop's Basic Developer Token tier limit. Rare for most accounts, but possible for very large advertisers. 1. Note the message in the Sync log. 2. Email **hello@sourceloop.ai** with the subject "Developer token upgrade" and your Customer ID. 3. We submit a Standard Tier upgrade request to Google with our use case; approval usually takes 5-7 business days. 4. During the wait, SourceLoop continues to push conversions for accounts under the Basic tier limit; only the over-limit account is affected. ## Step 6: For 'Insights data not updating' If campaign / ad group / ad spend and impressions look stale in SourceLoop: 1. **Check the last sync timestamp** on the Google Ads card. Insights sync is daily at 05:00 UTC. Within 24 hours is normal. 2. **Force a manual resync.** Click **Resync now** on the card. This triggers an immediate Insights sync that re-fetches the last 14 days. 3. **Check Google Ads quota.** At Google Ads -> Tools -> API Center, look at the daily API operation count. If you're near the quota, other tools are consuming it; investigate which. ## How to disconnect or reset If you want to fully reset the integration: - **Soft reset** (re-run the click mapping backfill): three-dot menu -> **Reset click mapping** on the Google Ads card. Useful if attribution history looks wrong. - **Hard reset** (disconnect entirely): see [Disconnect Google Ads from SourceLoop](/help/disconnect-google-ads-from-sourceloop/). ## When to email support If you've worked through the checklist and conversions still aren't pushing correctly, email **hello@sourceloop.ai** with: - The Google Ads card's current status - Two or three error messages from the Sync log (verbatim) - Your Google Ads Customer ID (visible on the Google Ads card) - The Conversion Action ID(s) you configured We respond within one business day. ## Frequently Asked Questions ### My Google Ads card shows 'Token expired'. What now? Click **Reconnect** on the Google Ads card. SourceLoop refreshes the access token via the stored refresh token automatically on every API call; 'Token expired' usually means the refresh token itself was revoked (someone removed SourceLoop access from the Google account, or the account's OAuth grants were cleared). A fresh OAuth flow fixes it. ### Conversions are firing in SourceLoop but not showing in Google Ads. What should I check? Five common causes. (1) GCLID missing, the visitor's session didn't have a `gclid` URL param, so there's no way to match to an ad click. (2) Conversion Action ID wrong, double-check the numeric ID in your SourceLoop config matches the Google Ads action. (3) Conversion in dedup window, repeated conversions of the same type from the same GCLID within 24 hours are skipped. (4) Pending verification status on the Google Ads action, normal for new actions, flips automatically once Google sees real conversions. (5) Conversion time outside the lookback window, Google Ads only accepts conversions within 90 days of the click. ### Google Ads says 'CONVERSION_ACTION_NOT_FOUND' in the sync log. Why? The Conversion Action ID in your SourceLoop config doesn't match a valid conversion action in the connected Google Ads account. Open Google Ads -> Tools -> Conversions, click into the right action, copy the ID from the URL, and update the SourceLoop config. ### My MCC user can't see the sub-account in the picker. What's missing? The Google user authorising the OAuth must have **Standard access or higher** on the specific Google Ads sub-account (not just on the MCC). Open Google Ads -> Tools -> Access and security -> Users, find the user, and verify their permission level. Read-only access doesn't include API write permissions. ### I'm getting 'developer token approval required' in the sync log. What does that mean? SourceLoop's Developer Token tier is Basic, which covers ad accounts up to about 15M monthly API operations. If your account exceeds that, Google Ads rejects requests until SourceLoop's Developer Token is upgraded. Email hello@sourceloop.ai with your Customer ID and we'll request the upgrade for you. ### Insights data (spend, impressions) is stale. When does it refresh? Insights sync runs daily at 05:00 UTC and re-fetches the last 14 days to catch Google's late attribution. To force an immediate refresh, click **Resync now** on the Google Ads card. Heads up, very recent campaign performance (last 1-3 days) often lags in Google Ads itself, you're not behind, Google is. ### How do I force a fresh 90-day click mapping backfill? Click the three-dot menu on the Google Ads card and select **Reset click mapping**. SourceLoop re-runs the 90-day GCLID-to-ad mapping backfill, useful if you suspect historical mappings are off due to attribution changes in Google Ads. --- # How to disconnect Google Ads from SourceLoop Disconnect SourceLoop from your Google Ads account. What stops, what is deleted immediately, what clears in 30 days, and how to confirm full removal. Source: https://sourceloop.ai/help/disconnect-google-ads-from-sourceloop/ Updated: 2026-05-28 --- This article covers the Google Ads disconnect flow end-to-end and the exact data SourceLoop holds, what gets deleted when, and how to confirm full removal. It exists so you (and your data-protection officer or compliance reviewer, if you have one) can answer every reasonable question about SourceLoop's handling of Google Ads-sourced data, and complies with Google's [API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy). ## Before you start Set expectations: - **Disconnecting on SourceLoop's side** revokes the OAuth grant on Google's side, deletes tokens from our vault, and removes the connection record + sync configs + pending pushes from our database, all immediately. - **Historical Insights data and click mappings** are deleted asynchronously within 30 days (per our privacy commitment). - **Neither action affects conversions already pushed to Google Ads.** Those stay in your Google Ads account, which is your data, not ours. ## Step 1: Disconnect on SourceLoop's side 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> Google Ads**. 3. On the Google Ads card, click the three-dot menu (top-right) and select **Disconnect**. 4. Confirm the disconnect dialog. The moment you confirm, SourceLoop runs the full disconnect for you. Here's what happens: 1. **Your Google authorization is revoked.** SourceLoop tells Google to invalidate the access we were granted, and we wipe our own copy of the authorization at the same time. From this point on, SourceLoop has no way to access your Google Ads account. 2. **Your conversion sync settings are cleared.** Every Conversion Action mapping, value source, attribution model, and dedup rule you configured for this account is removed. 3. **Pending conversion pushes are cancelled.** Anything still queued to be pushed to Google Ads is dropped. 4. **Your campaign performance history in SourceLoop is queued for deletion.** Spend, impressions, clicks, and the click-to-ad mappings used for attribution are removed asynchronously, usually within minutes, always within 30 days. 5. The Google Ads card on the Ad Platforms tab flips to **Disconnected**. ## Step 2: Revoke SourceLoop on Google's side (defence in depth) This step is optional because Step 1 already invokes Google's revoke endpoint. But for defence in depth, especially during compliance audits, you can also remove SourceLoop from your Google account's app permissions: 1. Open Google's My Account at [myaccount.google.com/permissions](https://myaccount.google.com/permissions). 2. Find **SourceLoop** in the list of apps with third-party access. 3. Click **Remove Access**. This is belt-and-braces, our Step 1 revoke usually removes SourceLoop from this list automatically, but doing it manually provides a documented audit trail in your Google account's activity log. ## Step 3: Decide what to do with your Google Ads conversion actions SourceLoop doesn't create conversion actions in Google Ads (you create them manually). So there's nothing for SourceLoop to clean up on Google Ads' side. After disconnect: - **Your conversion actions stay** in Google Ads with their accumulated conversion counts and values. - **Bidding strategies (tCPA, tROAS) using those actions** continue, just without new offline conversions arriving. - You can **disable, archive, or delete the conversion actions** manually in Google Ads -> Tools -> Conversions if you no longer want them. If you enabled Enhanced Conversions specifically because SourceLoop was pushing them, you can disable Enhanced Conversions on each action in Google Ads -> Tools -> Conversions -> [action] -> Enhanced conversions settings. ## What SourceLoop holds, and what gets deleted ### Deleted immediately at disconnect These go away the moment you click Disconnect: - **Your Google authorization** (revoked on Google's side and removed from ours) - **The connection itself**, including the Customer ID, account name, and last-sync information - **All your conversion sync settings**, Conversion Action mappings, value sources, attribution model choices, dedup windows - **All pending conversion pushes** that hadn't yet been sent to Google Ads - **Your sync run history** for this connection ### Deleted within 30 days These are queued for asynchronous deletion when you disconnect. Most clear within minutes; our privacy commitment guarantees completion within 30 days: - **Your campaign performance history in SourceLoop** (spend, impressions, and clicks per campaign / ad group / ad, collected during your connection) - **The click-to-ad lookups** used for attribution (so SourceLoop could tell which click drove which conversion) ### Never held in the first place For transparency, SourceLoop also does not, at any point, access: - Gmail, Drive, Calendar, or any other Google service beyond Google Ads - Personal info on your Google account beyond email and name (used solely to label the connection in our UI) - Customer-segment lists, audience data, or any first-party data inside Google Ads beyond what's needed to push conversion events ## Confirming the deletion For compliance audits or internal records, you can request a written confirmation of the deletion: 1. Email **hello@sourceloop.ai** with the subject "Confirm Google Ads deletion" and your Google Ads Customer ID. 2. We reply within 2 business days with deletion timestamps for each data category, signed under our privacy commitment. This confirmation is suitable for SOC2, ISO27001, and GDPR Article 17 (right to erasure) audit trails. ## Reconnecting later You can reconnect at any time. Because disconnect wipes the connection and its settings, you're starting fresh: 1. Sign in to SourceLoop. 2. Open **Setup -> Ad Platforms -> Google Ads**. 3. Click **Connect**. 4. Run the OAuth flow. 5. Pick the same Customer ID in the picker. 6. **Set up your conversion sync again**, Conversion Action mappings, attribution model, value source, dedup window. You'll re-enter these in the Google Ads drawer. Your previous campaign performance history (spend, impressions, clicks) is gone, that's what the 30-day deletion covered. Reconnecting pulls in 90 days of fresh click history so attribution kicks back in immediately. ## Privacy and security For full transparency: - **What we ask for:** read and write access to the specific Google Ads account(s) you authorise. Nothing else, no Gmail, no Drive, no Calendar, no other Google service. - **How we store your authorization:** encrypted at rest with industry-standard practices. The raw access token never appears in logs or in our UI. - **Automatic renewal:** SourceLoop renews access in the background as needed so you don't have to reconnect. The renewal stops the moment you disconnect. - **Third parties:** SourceLoop does not sell, transfer, or share Google Ads data with anyone outside SourceLoop. The data is used only to render your attribution analytics and to push conversions back to your own Google Ads account. - **Limited Use:** SourceLoop complies with Google's [Limited Use](https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes) requirements, ad data is used only to provide the user-facing features described in our [Privacy Policy](/privacy/). - **Encryption in transit:** all communication with Google's APIs uses TLS 1.2+. ## When to email support For anything outside the standard flow: - **"I disconnected by mistake"** → email hello@sourceloop.ai; we can prioritise restoring the configuration if you act within 24 hours (before async cleanup runs) - **"Our compliance team needs a written data-processing agreement (DPA)"** → we provide a signed DPA on request - **"An auditor needs evidence of deletion completion"** → we provide a written confirmation keyed to your Customer ID with timestamps for each data category - **"Google's Connected Apps still lists SourceLoop"** → complete Step 2 above; if it still shows, email us with screenshots Email **hello@sourceloop.ai** for any of the above. We respond within one business day. ## Frequently Asked Questions ### How quickly does SourceLoop stop pushing conversions after I disconnect? Immediately. The moment you confirm Disconnect, SourceLoop deletes the OAuth tokens from our vault and removes the connection record from the database. The 2-minute flush cycle queries for active connections only, so the very next cycle excludes yours. In-flight pushes already in transit to Google Ads may complete (they take milliseconds), but no new pushes are queued. ### Does disconnecting delete the conversion data I've already pushed to Google Ads? No. Conversions already pushed to Google Ads stay in Google Ads. SourceLoop cannot retroactively remove conversions from Google Ads (the Google Ads API doesn't expose bulk delete for offline conversions). Your historical bidding signals stay intact in your ad account. ### What happens to my Google authorization after disconnect? SourceLoop tells Google to revoke the access we were granted, and at the same time we wipe our copy of the authorization from our systems. Both sides are cleared within seconds of you clicking Disconnect. It can't be recovered or reused. ### What gets deleted immediately vs within 30 days? Deleted immediately, your Google authorization, all conversion sync settings you configured, sync run history, and any pending conversion pushes. Deleted within 30 days, your campaign performance history in SourceLoop (spend, impressions, clicks per campaign/ad group/ad) and the click-to-ad lookups used for attribution. These typically clear within minutes; 30 days is the outer limit we commit to. ### How do I verify the deletion happened? Email hello@sourceloop.ai with the subject "Confirm Google Ads deletion" and your Customer ID. We provide a written confirmation including the deletion timestamps for each data category, suitable for compliance audits. ### Will reconnecting later restore my old conversion sync configuration? No. Disconnecting wipes the configuration along with the rest of the connection. When you reconnect, you'll be starting fresh, you'll set up your Conversion Action mappings again. The 30-day window is for the historical performance data, not for your settings. ### Does Google's User Data Policy apply here? Yes. SourceLoop complies with Google's API Services User Data Policy, including the Limited Use requirements. We only access the `adwords` scope on the specific accounts you authorise, we never sell or transfer user data to third parties, we don't use ad data for advertising, and we delete all data on request. ### Will disconnecting Google Ads affect my other ad platform connections? No. Each ad platform connection is independent. Disconnecting Google Ads doesn't affect Meta, TikTok, LinkedIn, Microsoft Ads, or any CRM, payment, form, meeting, or chat integration. --- # Meta Ads Conversion Tracking & Attribution Setup Guide Connect Facebook and Instagram Ads to push real conversions back to your Meta Pixel via the Conversions API. Multi ad-account and Lead Ads support. Source: https://sourceloop.ai/help/connect-meta-ads/ Updated: 2026-05-28 --- Connecting Meta Ads to SourceLoop opens up the full conversion loop for Facebook and Instagram campaigns. Every time a tracked lead converts on your site (form submission, meeting booked, payment received), SourceLoop sends that conversion back to Meta via the Conversions API. The bidding algorithms train on real revenue instead of relying only on browser-side pixel events that get blocked by privacy settings. This article covers the OAuth connect flow only. After connecting, see: - [Configure Meta Conversions API sync](/help/configure-meta-conversions-api/) - [Troubleshoot Meta Ads sync issues](/help/troubleshoot-meta-ads-sync/) ## Why connect Meta Ads to SourceLoop? The Meta Pixel that fires in browsers covers maybe 60% of conversions on a good day. iOS tracking restrictions, ad blockers, third-party cookie restrictions, and consent banners eat the rest. The Conversions API closes that gap by sending events server-side from your backend (in this case, SourceLoop) instead of the browser. What SourceLoop adds: - **Server-side conversion push** via the Conversions API, immune to browser-side blocking - **Click ID matching** using `fbc` (click cookie) and `fbp` (browser cookie) plus hashed email and phone - **Revenue values** sent on every conversion when payment integrations are connected - **Event-level dedup** so server-side and browser-side reporting of the same conversion don't double-count - **Lead Ads capture** for Facebook and Instagram Lead Ads (with the right scopes during connect) - **Multi-touch attribution** within SourceLoop, while feeding Meta the conversion signals it needs for tROAS and CAPI-driven bidding ## Before you start You'll need: - A **SourceLoop workspace** with the [tracking pixel installed](/help/install-the-tracking-pixel/) - A **Meta Business Manager** account that owns the ad accounts you want to connect - A Meta user with **Admin** access on the Business - An existing **Meta Pixel** (or Dataset) attached to the ad account, you'll need its Pixel ID in the next article - **Admin** or **Owner** role in SourceLoop (Editors can't add integrations) ## Step 1: Open the Ad Platforms page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Setup** in the left sidebar. 3. Click the **Ad Platforms** tab. 4. Scroll to the **Meta Ads** card and click **Connect**. You'll be redirected to Meta's OAuth consent screen on facebook.com. ## Step 2: Authorise SourceLoop on Meta 1. Sign in to Facebook with the user that has Admin access on your Business Manager. 2. Review the scopes SourceLoop is requesting (ads management, ads insights, Business enumeration, plus Pages scopes for Lead Ads capture). 3. **Pick which Business and ad accounts SourceLoop should access.** Meta lets you scope the OAuth grant to a subset; uncheck anything SourceLoop shouldn't see. 4. Click **Continue**. Meta redirects you back to SourceLoop with a short-lived authorization code, which SourceLoop exchanges for a long-lived access token (good for 60 days). The token is stored encrypted. > **Recommended: switch to a System User Access Token after connect** > Long-lived user tokens expire every 60 days, which means an annoying reconnect on a fixed cadence. For production use, generate a System User Access Token in Meta Business Manager and paste it into SourceLoop's Meta drawer (see [Configure Meta Conversions API sync](/help/configure-meta-conversions-api/)). System User tokens don't expire. ## Step 3: Pick the Meta ad account If your Meta user has access to more than one ad account (common for agencies and teams), SourceLoop shows a picker with every account, including currency and timezone. 1. Find the ad account you want to connect. 2. Click **Connect this account**. SourceLoop creates a connection for that specific ad account. The Meta Ads card flips to **Connected** with the account name and Account ID. > **To connect another account, repeat the flow** > Each Meta ad account is its own connection. To connect multiple accounts (e.g., one per brand or per region), run the Connect Meta Ads flow once per account. They sync and push conversions independently. ## Step 4: Add your Pixel ID and CAPI token To actually push conversions, SourceLoop needs two pieces of information that only you can see on Meta's side: your **Pixel ID** and an access token that's authorised to write to that Pixel. The full walk-through of how to find each one is in [Configure Meta Conversions API sync](/help/configure-meta-conversions-api/). Short version: 1. In SourceLoop's Meta drawer, find the **CAPI configuration** section. 2. Paste your **Pixel ID** (visible in Meta Events Manager). 3. Paste your **System User Access Token** with `ads_management` permission for that Pixel. 4. Click **Save**. The connection status flips to **Active**, and the daily Insights sync + the 2-minute conversion-push cycle both start running. ## What gets synced Once connected, SourceLoop runs two flows automatically: **PULL (Insights sync, daily at 05:00 UTC):** - Campaign, ad set, and ad hierarchy - Daily spend, impressions, clicks, reach, and frequency per level - 14-day rolling re-sync to catch Meta's late attribution - Refreshed in SourceLoop dashboards within minutes of each sync run **PUSH (Conversions API, every 2 minutes):** - Every SourceLoop conversion where the visitor's session carries an `fbc` cookie, an `fbp` cookie, or a hashed email / phone that matches a Meta user - Conversion value (currency) and currency code when configured - Stable event IDs for dedup against any browser-side pixel events fired on the same action - Event source URL (the landing page where the conversion happened) ## What's next - **Pick which SourceLoop events map to which Meta event_name (Lead, Purchase, etc.):** [Configure Meta Conversions API sync](/help/configure-meta-conversions-api/). - **Troubleshoot** any push errors or stale syncs: [Troubleshoot Meta Ads sync issues](/help/troubleshoot-meta-ads-sync/). - **Disconnect or reset** the integration: [Disconnect Meta Ads from SourceLoop](/help/disconnect-meta-ads-from-sourceloop/). ## Frequently Asked Questions ### Do I need a Meta Business Manager account? Yes. SourceLoop connects to ad accounts owned by your Meta Business, so a Business Manager account is the entry point. If you have a personal ad account that hasn't been claimed by a Business yet, claim it first at business.facebook.com. ### Which ad accounts can I connect? Every active ad account your Meta user has access to. After OAuth, SourceLoop shows the list, currency and timezone included, and you pick one per connection. Repeat the flow to connect multiple accounts. ### What's the difference between my Pixel ID and the System User Access Token? The Pixel ID is the public identifier of your Meta Pixel (or Dataset) where events should be reported. The System User Access Token is the credential SourceLoop uses to call the Conversions API against that Pixel. You enter both in the Meta drawer after the initial OAuth connection. We walk through finding each one in the Configure article. ### My Meta token expires every 60 days, do I need to reconnect manually? SourceLoop monitors token health and emails you a reminder when the token is approaching expiry. If a Long-Lived Access Token is in use, SourceLoop renews it automatically when it's still valid. If your Pixel uses a System User token (recommended for stability), those don't expire at all. ### Does this work for Instagram Ads too? Yes. Meta Ads is the unified surface for both Facebook and Instagram placements. Conversions captured by SourceLoop are reported with `action_source = 'website'` to the Conversions API and Meta routes them to whichever campaign and placement drove the click. ### What scopes does SourceLoop request from Meta? The required scopes are `ads_management` (push conversions via Conversions API), `ads_read` (pull spend and impressions), `business_management` (enumerate your ad accounts), plus four Page-related scopes used only if you also enable Lead Ads capture, `pages_show_list`, `pages_manage_metadata`, `pages_read_engagement`, `leads_retrieval`. We never ask for personal Facebook content like messages or friends. ### Can I sync more than one Meta ad account into the same SourceLoop workspace? Yes. Each ad account is a separate connection. Repeat the Connect Meta Ads flow once per account; they sync and push conversions independently. --- # How to Sync Revenue and Offline Conversions in Meta Ads Set up Meta Conversions API push from SourceLoop. Find your Pixel ID, generate a System User token, map events to Meta names, and test the flow. Source: https://sourceloop.ai/help/configure-meta-conversions-api/ Updated: 2026-05-28 --- After you've [connected Meta Ads to SourceLoop](/help/connect-meta-ads/), the next step is wiring up the Conversions API push. You'll paste a Pixel ID and access token, then map which SourceLoop events get sent to which Meta event_name with what value. This is the part that determines whether Meta's bidding optimises on real revenue or just whatever the browser pixel could see. ## Before you start You'll need: - [Meta Ads connected](/help/connect-meta-ads/) (the card on the Ad Platforms tab shows the ad account name) - Access to **Meta Events Manager** for the Pixel you want to report to - Access to **Meta Business Settings** to generate a System User Access Token (recommended) - A list of SourceLoop conversions you want to push and what Meta event_name each should map to (`Lead`, `Schedule`, `Purchase`, `Subscribe`, custom) ## Step 1: Find your Pixel ID 1. Sign in to [Meta Business Suite](https://business.facebook.com/) with a Business Admin user. 2. Open **Events Manager** (left sidebar). 3. Pick the **Pixel** (or **Dataset**) attached to the ad account you connected to SourceLoop. 4. On the Pixel's **Overview** tab, copy the **Pixel ID**, it's a 15-16 digit number shown at the top of the page. Hold onto this; you'll paste it into SourceLoop in step 3. ## Step 2: Generate a System User Access Token User tokens expire every 60 days. System User tokens don't expire. For anything production-grade, use a System User token. 1. In Meta Business Suite, open **Business Settings** (gear icon, top-right). 2. Go to **Users -> System Users**. 3. Either pick an existing System User or click **Add** to create a new one. Name it something descriptive (e.g., "SourceLoop CAPI"). 4. With the System User selected, click **Add Assets** and assign: - The **ad account** you connected to SourceLoop (with Manage permission) - The **Pixel** you'll be reporting to (with Manage permission) 5. Click **Generate New Token**. 6. Select the SourceLoop app from the dropdown (or your Business' default app for Conversions API). 7. Check **ads_management**. (You can leave other scopes unchecked.) 8. Set token expiry to **Never** if available, otherwise the longest option. 9. Click **Generate token** and copy the token shown. **Save it somewhere safe** — Meta only shows it once. ## Step 3: Paste both values into SourceLoop 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> Meta Ads**. 3. In the **CAPI configuration** section of the drawer: - Paste the **Pixel ID** from Step 1 - Paste the **System User Access Token** from Step 2 - (Optional) Paste a **Test event code** for end-to-end testing (see FAQ above) 4. Click **Save**. SourceLoop validates the token against Meta's API. If it's valid, the connection status flips to **Active**. ## Step 4: Add your conversion sync mappings Now wire up which SourceLoop events get pushed to which Meta event_name. 1. In the Meta drawer, scroll to **Conversion sync**. 2. Click **Add conversion mapping**. 3. Pick the **Trigger event type**: - **Conversion created** — fires for every new lead conversion (form, meeting, chat with email captured, initial payment) - **Expected revenue updated** — fires when the lead's expected revenue changes - **Realized revenue updated** — fires when a payment integration confirms revenue 4. (Optional) Set a **Trigger event name filter** to scope this mapping to a specific source (e.g., `stripe_charge.succeeded`). 5. Pick an **Attribution model**: **Last touch** or **First touch**. 6. Pick the **Meta event_name** the conversion should map to: - **Lead** — most common for forms / meetings / chats - **Schedule** — for meeting bookings - **Subscribe** — for new SaaS subscriptions - **Purchase** — for one-off product sales - **CompleteRegistration**, **AddPaymentInfo**, **InitiateCheckout** — funnel events - **Custom event name** — type any string (Meta supports custom events too) 7. Toggle **Include value** if you want to send a revenue figure. Pick the value source (`quote_value`, `sales_value`, or fixed amount) and currency. 8. Set the **dedup window** in minutes (default 1440). Meta's own dedup window is 7 days; SourceLoop respects whichever is shorter. 9. Click **Save**. SourceLoop starts pushing matching conversions on the next 2-minute flush cycle. Allow a few minutes for the first event to appear in Meta Events Manager. > **Always start with a test_event_code** > Enable a test code in Events Manager's Test events tab and paste it in SourceLoop's CAPI configuration. Fire a real conversion (incognito, with utm params, complete checkout / form), and watch it appear in Events Manager's Test events view within seconds. Confirm Match Quality is green, then remove the test code so events flow to production. ## What gets sent in each event For every matching SourceLoop conversion, the push to Meta includes: - **event_name** from your mapping (e.g., `Lead`, `Purchase`) - **event_time** in Unix seconds - **event_id** — a stable identifier per SourceLoop conversion, used by Meta to dedup against browser-side pixel events - **action_source** = `website` - **event_source_url** — the landing page where the conversion happened - **user_data** — the matchable signals SourceLoop has captured: - `fbc` (click cookie) when present - `fbp` (browser cookie) when present - hashed email (SHA256) - hashed phone (SHA256) - hashed first / last name when available - client IP and user agent - **custom_data** — `value` and `currency` when value is included; plus any custom properties you've configured The richer the `user_data`, the better Meta's match rate. Click ID alone gives ~95% match; email + phone alone gives ~80%; both together approaches 100%. ## What's next - **Troubleshoot pushes that aren't appearing in Events Manager:** [Troubleshoot Meta Ads sync issues](/help/troubleshoot-meta-ads-sync/). - **Add more conversion mappings** for separate funnels (lead vs purchase vs subscription): repeat the Step 4 flow with each Meta event_name. ## Frequently Asked Questions ### Where do I find my Meta Pixel ID? In Meta Events Manager (business.facebook.com/events_manager). Pick the Pixel you want to use, look at the Overview tab, the Pixel ID is the 15-16 digit number at the top. You can also click the Settings tab for the same value plus extra details. ### What's a System User Access Token and why is it better than a normal token? A System User in Meta Business Manager is a non-human service account whose access tokens never expire (unlike user tokens which expire every 60 days). For production Conversions API push, this is the recommended path. You generate one in Business Settings -> Users -> System Users -> [user] -> Generate New Token, scoped to ads_management with access to your Pixel. ### What permissions does the System User need? At minimum, the `ads_management` permission with access to the Pixel you're reporting to and the ad account that owns it. The System User itself needs to be added to the Pixel's Assigned Users list as either Admin or Standard. Without that assignment, the token can authenticate but cannot write events. ### Can I use a different event_name per conversion source? Yes. SourceLoop's Conversion sync configuration lets you map different SourceLoop event types (form submission, meeting booked, payment) to different Meta event_name values (Lead, Schedule, Purchase). Add a config row per mapping. You can even send the same SourceLoop event to multiple event names if you want both a Lead and a Purchase fired from the same checkout completion. ### How does dedup work between browser-side Pixel events and CAPI events from SourceLoop? SourceLoop generates a stable `event_id` per SourceLoop conversion. If your browser-side Pixel also fires with the same `event_id` (most server-side setups handle this), Meta dedupes within a 7-day window. If you don't share event_ids, both events count, that's why most teams either switch to CAPI-only or wire matching event IDs in their browser-side tags. ### What's the test_event_code for and where do I find it? While testing, you can enable a "Test event code" in Meta Events Manager's Test events tab. Paste the same code in SourceLoop's Meta drawer. CAPI events sent with that code are routed to the Test events view instead of production, so you can verify the integration works without polluting your real campaign data. Remove the code once you're confident the integration is right. ### My business uses Datasets instead of Pixels. Does this still work? Yes. The "Pixel ID" field in SourceLoop accepts either a Pixel ID or a Dataset ID (Meta uses the same identifier format and the same Conversions API endpoint for both). Paste whichever is set up in your Business. --- # How to troubleshoot Meta Ads sync issues Checklist for Meta Ads sync problems, covering token expiry, missing CAPI events, match quality, permissions, and verifying in Events Manager. Source: https://sourceloop.ai/help/troubleshoot-meta-ads-sync/ Updated: 2026-05-28 --- Meta Ads sync issues fall into a handful of buckets. Work through this checklist in order. ## Before you start Have these tabs open: - **SourceLoop's Meta Ads card** at **Setup -> Ad Platforms -> Meta Ads** - **Meta Events Manager** for the Pixel you're reporting to - **Meta Business Settings** in case you need to regenerate a System User token - The most recent **Sync log** entry on the Meta card ## Step 1: Check the connection status 1. Open **Setup -> Ad Platforms -> Meta Ads** in SourceLoop. 2. Look at the card: - **Active** with recent Last sync → healthy; problem is downstream - **Active** with stale Last sync (>24 hours) → Insights sync stuck - **Token expired** → reconnect or update the token - **Disconnected** → run the Connect flow ## Step 2: For 'Token expired' / 'Disconnected' The fastest fix is to update the token directly in the Meta drawer: 1. In Meta Business Settings, generate a new **System User Access Token** with `ads_management` and the correct Pixel assignment (see [Configure Meta Conversions API sync](/help/configure-meta-conversions-api/) for the System User setup). 2. In SourceLoop, open **Setup -> Ad Platforms -> Meta Ads**. 3. Paste the new token in the **CAPI configuration** section and click **Save**. 4. The connection flips back to **Active** immediately. If you'd rather run a fresh OAuth flow instead, click **Reconnect** on the card. Note: user tokens from OAuth expire every 60 days, so you'll see this issue again. System User tokens don't expire. ## Step 3: For 'conversions not appearing in Events Manager' This is the most common issue. Walk through: 1. **Open the Sync log** on the Meta card (three-dot menu). 2. Find a conversion you expected. Each push is logged with the event_name, Pixel ID, value, and status: - **sent** → SourceLoop successfully called the Conversions API. The event may take 20-60 seconds to appear in Events Manager. - **pending_identity** → the visitor had no usable identity signal (no `fbc`, no `fbp`, no email, no phone). Skip these. - **error** → Meta rejected the push. Click the row to see the exact error message. Common errors and fixes: - **`Invalid OAuth access token`** → token expired or revoked. See Step 2. - **`Pixel not found / unauthorized`** → wrong Pixel ID, or the System User isn't assigned to the Pixel. - **`Permission OAuthException`** → token has correct format but isn't authorised for this Pixel. Regenerate with `ads_management` and confirm the System User is assigned to the Pixel. - **`Field "event_name" is invalid`** → custom event_name contains spaces or unsupported characters. Use camelCase or underscored names. - **`server_event has no fields`** → SourceLoop sent an event with no matchable data and Meta rejected it. Same root cause as `pending_identity` — improve the identity capture (see Step 5). ## Step 4: For 'test events not showing in Events Manager Test events tab' If you set a test_event_code but events aren't appearing: 1. Confirm the **test_event_code in SourceLoop matches** the one in Events Manager Test events tab. They must be identical, case-sensitive. 2. The code is **per Pixel**, make sure you're looking at the Test events tab for the same Pixel ID you configured in SourceLoop. 3. After firing a conversion, **refresh the Test events tab manually**, it doesn't always live-update. 4. Confirm the conversion **actually fired in SourceLoop** by checking the Contacts Hub for a new entry with a recent timestamp. ## Step 5: For 'poor Match Quality score' Match Quality is Meta's measure of how often your CAPI events successfully match to real Meta users. Low scores mean Meta can't optimise bidding well off your data. To improve Match Quality: 1. **Make sure the SourceLoop tracking pixel is installed on every landing page**, not just the conversion page. The `fbc` cookie is set on first ad-click landing; if the page doesn't load the SourceLoop tracker, that cookie never gets captured. 2. **Capture email and phone in your forms.** Meta hashes them client-side before sending and uses the hashes to match against its user database. Email + phone together produce match rates near 100%. 3. **Don't push events for anonymous visitors.** SourceLoop already skips conversions with no identity signal (`pending_identity` status). Make sure your conversion sources are configured to require email at minimum. 4. **Send `fbc` and `fbp` reliably.** These are set by the SourceLoop tracker on first ad-click and propagate through to the conversion event. If you have aggressive cookie-clearing or privacy plugins on your site, they may strip these. ## Step 6: For 'Insights data not updating' If campaign / ad set / ad spend and impressions look stale in SourceLoop: 1. **Check the last sync timestamp** on the Meta card. Insights sync runs daily at 05:00 UTC. Within 24 hours is normal. 2. **Force a manual resync.** Click **Resync now** on the card. This triggers an immediate Insights sync re-fetching the last 14 days. 3. **Check Meta's own reporting.** If Meta Ads Manager shows the same lag (common for the last 1-3 days), the data isn't ready yet on Meta's end. SourceLoop can only show what Meta has. ## How to disconnect or reset - **Soft reset** (re-run the click-mapping backfill if you suspect attribution issues): three-dot menu -> **Reset click mapping** on the Meta card. - **Hard reset** (disconnect entirely): see [Disconnect Meta Ads from SourceLoop](/help/disconnect-meta-ads-from-sourceloop/). ## When to email support If you've worked through the checklist and CAPI events still aren't appearing correctly, email **hello@sourceloop.ai** with: - The Meta Ads card's current status - Two or three error messages from the Sync log (verbatim, including the Meta-side error message) - Your **Ad Account ID** (visible on the Meta card) - Your **Pixel ID** We respond within one business day. ## Frequently Asked Questions ### My Meta card shows 'Token expired'. What now? Reconnect from the Meta card to refresh the user token, or paste a System User Access Token from Meta Business Settings (which doesn't expire). If you reconnect with a user token, you'll see this again in 60 days. Switching to a System User token is the durable fix. ### Conversions are firing in SourceLoop but not showing in Meta Events Manager. What should I check? Five common causes. (1) Test event code mismatch, if you set a test_event_code in SourceLoop, events go to Test events only, not the live Pixel feed. Remove the test code to send to production. (2) Pixel ID wrong, double-check the value matches the Pixel ID in Events Manager. (3) Token missing ads_management permission, regenerate the System User token with the right scope. (4) System User isn't assigned to the Pixel, in Business Settings, assign the user to the Pixel as Admin. (5) The events take 20-60 seconds to appear in Events Manager UI, that's normal. ### Meta says 'Invalid OAuth access token' in the sync log. Why? Three possible causes. (1) The token has expired (user tokens last 60 days; System User tokens with "Never" expiry don't). (2) Someone removed SourceLoop from your Business' Connected Apps. (3) The token was generated for a different Business / ad account than the one you connected. Reconnect with a valid token bound to the right Business. ### My Match Quality score is poor. How do I improve it? Match Quality is Meta's measure of how well CAPI events match to real Meta users. To improve it, make sure SourceLoop is capturing as much identity data as possible, ensure the tracking pixel is installed early on every page (so fbc/fbp cookies get set), capture email and phone where possible (forms with these fields produce much better match), and don't fire CAPI for visitors who never gave any identifying signal. ### I'm getting 'Permission OAuthException' errors. What does that mean? The token you provided doesn't have permission to write to the Pixel ID you specified. Either the System User isn't assigned to the Pixel (fix in Business Settings -> Users -> System Users -> [user] -> Assets), or the token was generated for a different Pixel. ### How do I send a test event to verify the integration? In Meta Events Manager, open the **Test events** tab. Copy the test code shown. Paste it into SourceLoop's Meta drawer in the CAPI configuration section. Fire a real conversion on your site, the event should appear in the Test events view within seconds, with match quality and parameter values visible. ### Insights data (spend, impressions) is stale. When does it refresh? Insights sync runs daily at 05:00 UTC and re-fetches the last 14 days to catch Meta's late attribution. To force an immediate refresh, click Resync now on the Meta card. Note that very recent campaign performance (last 1-3 days) often lags in Meta itself, you're not behind, Meta is. ### Can I disconnect just the CAPI push but keep the Insights sync? Not directly. Disconnecting removes the connection entirely. To stop CAPI pushes without disconnecting, remove all conversion mappings in the Conversion sync section. The connection stays Active and continues to pull Insights, but no events are pushed. --- # How to disconnect Meta Ads from SourceLoop Disconnect SourceLoop from your Facebook and Instagram Ads. What stops, what is deleted immediately, what clears in 30 days, and how to confirm removal. Source: https://sourceloop.ai/help/disconnect-meta-ads-from-sourceloop/ Updated: 2026-05-28 --- This article covers the Meta Ads disconnect flow end-to-end, what stops immediately, what data we delete, what clears within 30 days, and how to confirm full removal. It exists so you (and your data-protection officer or compliance reviewer) can answer every reasonable question about SourceLoop's handling of Meta-sourced data, and it complies with Meta's [Platform Terms](https://developers.facebook.com/terms/) including the data deletion requirements for Conversions API integrations. ## Before you start Set expectations: - **Disconnecting on SourceLoop's side** revokes your Meta authorization, deletes the Pixel ID and CAPI token from SourceLoop, and clears every other piece of configuration for this connection, all immediately. - **Historical campaign performance data** is deleted asynchronously within 30 days. - **Neither action affects conversions already pushed to Meta.** Those stay in your Pixel's event history and continue to inform bidding signals. ## Step 1: Disconnect on SourceLoop's side 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> Meta Ads**. 3. On the Meta Ads card, click the three-dot menu (top-right) and select **Disconnect**. 4. Confirm the disconnect dialog. The moment you confirm, SourceLoop runs the full disconnect for you. Here's what happens: 1. **Your Meta authorization is revoked.** SourceLoop tells Meta to invalidate the access we were granted, and we wipe our own copy at the same time. From this point on, SourceLoop has no way to access your Meta account. 2. **Your Pixel ID and CAPI token are cleared from SourceLoop.** The Pixel itself stays in Meta Events Manager (untouched). The System User Access Token stays in your Meta Business Settings; SourceLoop just no longer holds a copy. 3. **Any Lead Ads webhook subscriptions are cancelled.** If you'd enabled Lead Ads capture for any Pages, SourceLoop calls Meta to unsubscribe so no new Lead Ads submissions flow into SourceLoop. 4. **Your conversion sync settings are cleared.** Every conversion mapping, value source, event_name choice, attribution model, and dedup rule for this account is removed. 5. **Pending conversion pushes are cancelled.** Anything still queued to be sent to the Conversions API is dropped. 6. **Your campaign performance history is queued for deletion.** Spend, impressions, reach, and click history are removed asynchronously, usually within minutes, always within 30 days. 7. The Meta Ads card on the Ad Platforms tab flips to **Disconnected**. ## Step 2: Remove SourceLoop from Meta Business Settings (recommended) This step is optional, our Step 1 already revokes the OAuth grant on Meta's side. But for clean audit trails (and for compliance reviews), you can also remove SourceLoop's app from your Business's Connected Apps list: 1. Sign in to [Meta Business Suite](https://business.facebook.com/) as a Business Admin. 2. Open **Business Settings** (gear icon). 3. Go to **Integrations -> Apps** (or under **Connected Apps**, depending on UI variant). 4. Find **SourceLoop** in the list. 5. Click **Remove** and confirm. Meta logs the removal in your Business audit trail. Defence in depth. ## Step 3: Decide what to do with your Pixel and System User SourceLoop doesn't create or modify Pixels, System Users, or conversion events on Meta's side. So there's nothing on Meta's end for SourceLoop to clean up. But you can: - **Keep the Pixel** as-is. It continues to receive any browser-side events (Meta Pixel JavaScript) plus any other Conversions API integrations you have. - **Delete the System User Access Token** that was issued for SourceLoop, if you don't plan to reconnect. Go to **Business Settings -> Users -> System Users -> [user] -> Tokens** and delete the token. Belt-and-braces, since the underlying access has already been revoked. - **Reassign or delete the System User itself** if it existed only for SourceLoop. Optional. ## What SourceLoop holds, and what gets deleted ### Deleted immediately at disconnect These go away the moment you click Disconnect: - **Your Meta authorization** (revoked on Meta's side and removed from ours) - **Your Pixel ID and CAPI token** as configured in SourceLoop (the Pixel itself in Meta is untouched) - **The connection itself**, including the Ad Account ID, account name, currency, and timezone - **All your conversion sync settings**, event_name mappings, value sources, attribution choices, dedup windows - **Any Lead Ads webhook subscriptions** on the Pages you'd authorised (unsubscribed via Meta's API) - **All pending conversion pushes** that hadn't yet been sent to the Conversions API - **Your sync run history** for this connection ### Deleted within 30 days These are queued for asynchronous deletion when you disconnect. Most clear within minutes; our privacy commitment guarantees completion within 30 days: - **Your campaign performance history in SourceLoop** (spend, impressions, reach, frequency, clicks per campaign / ad set / ad, collected during your connection) - **The click-to-ad lookups** used for attribution ### Never held in the first place For transparency, SourceLoop also does not, at any point, access: - Your personal Facebook messages, friends list, profile content, or any non-advertising data - Any organic Page content (we only touch Pages you authorise for Lead Ads capture, and only to subscribe / read Lead form responses) - Audience data, Custom Audiences, or Lookalike Audiences inside your ad account beyond what's needed to push conversion events ## Confirming the deletion For compliance audits or internal records, you can request written confirmation: 1. Email **hello@sourceloop.ai** with the subject "Confirm Meta Ads deletion" and your Meta Ad Account ID. 2. We reply within 2 business days with deletion timestamps for each data category, signed under our privacy commitment. This confirmation is suitable for SOC2, ISO27001, and GDPR Article 17 (right to erasure) audit trails. ## Reconnecting later You can reconnect at any time. Because disconnect wipes the connection and its settings, you're starting fresh: 1. Sign in to SourceLoop. 2. Open **Setup -> Ad Platforms -> Meta Ads**. 3. Click **Connect**. 4. Run the OAuth flow. 5. Pick the same Ad Account in the picker. 6. **Re-enter your Pixel ID and a fresh System User Access Token** in the CAPI configuration. 7. **Set up your conversion sync mappings again**, event_name, attribution model, value source, dedup window. Your previous campaign performance history is gone, that's what the 30-day deletion covered. Reconnecting pulls in fresh data going forward. ## Privacy and security For full transparency: - **What we ask for:** read and write access to the Meta ad accounts and Pages you authorise. Nothing else. No Messenger, no personal posts, no friends list, no Marketplace. - **How we store your authorization:** encrypted at rest with industry-standard practices. The raw access token never appears in logs or in our UI. - **System User tokens:** if you provide a System User Access Token (recommended), it stays encrypted; we never display it in plaintext after save, and you can rotate it any time without notifying us. - **Third parties:** SourceLoop does not sell, transfer, or share Meta-sourced data with anyone outside SourceLoop. The data is used only to render your attribution analytics and to push conversions back to your own Meta account. - **Meta Platform Terms:** SourceLoop complies with Meta's [Platform Terms](https://developers.facebook.com/terms/) and Conversions API terms, ad data is used only to provide the user-facing features described in our [Privacy Policy](/privacy/). - **Encryption in transit:** all communication with Meta's APIs uses TLS 1.2+. ## When to email support For anything outside the standard flow: - **"I disconnected by mistake"** → email hello@sourceloop.ai; we can prioritise restoring the configuration if you act within 24 hours - **"Our compliance team needs a written data-processing agreement (DPA)"** → we provide a signed DPA on request - **"An auditor needs evidence of deletion completion"** → we provide a written confirmation keyed to your Ad Account ID with timestamps for each data category - **"Meta still shows SourceLoop in my Business' connected apps"** → complete Step 2 above; email us if it still shows Email **hello@sourceloop.ai** for any of the above. We respond within one business day. ## Frequently Asked Questions ### How quickly does SourceLoop stop pushing conversions after I disconnect? Immediately. The moment you confirm Disconnect, SourceLoop revokes your Meta authorization, wipes our copy of the access token and CAPI token, and clears the connection. The 2-minute Conversions API flush cycle queries only active connections, so the very next cycle skips yours. In-flight pushes already in transit complete in milliseconds; no new pushes are queued. ### Does disconnecting delete the events I've already pushed to Meta? No. Events already received by the Conversions API stay in Meta. Bidding signals trained on those events continue to inform optimisation. SourceLoop cannot retroactively remove events from Meta's pipeline (the Conversions API doesn't expose bulk delete for past events). ### What happens to the Pixel ID and System User token I configured? Both are wiped from SourceLoop the moment you disconnect. The Pixel itself stays in Meta Events Manager, untouched. The System User Access Token stays valid in Meta Business Settings until you choose to delete it; SourceLoop never had the ability to revoke a System User token (only you can do that on Meta's side). ### What gets deleted immediately vs within 30 days? Deleted immediately, your Meta authorization, the Pixel ID and CAPI token stored in SourceLoop, all conversion sync settings, sync history, and any pending pushes. Deleted within 30 days, your campaign performance history in SourceLoop (spend, impressions, reach, frequency per campaign / ad set / ad) and the click-to-ad lookups used for attribution. ### How do I verify the deletion happened? Email hello@sourceloop.ai with the subject "Confirm Meta Ads deletion" and your Meta Ad Account ID. We provide a written confirmation with deletion timestamps for each data category, suitable for compliance audits. ### Will reconnecting later restore my Pixel ID and conversion mappings? No. Disconnect wipes everything tied to the connection. Reconnecting starts fresh, you'll re-enter the Pixel ID, paste a fresh System User Access Token, and reconfigure your conversion sync mappings. The 30-day window is only for historical campaign data, not for your settings. ### I also enabled Lead Ads capture. Does disconnect remove the Lead Ads webhook subscriptions? Yes. SourceLoop calls Meta to unsubscribe from any Lead Ads webhook subscriptions on the Pages you'd authorised, as part of the disconnect flow. The Pages themselves are untouched. ### Will disconnecting Meta affect my other ad platform connections? No. Each ad platform connection is independent. Disconnecting Meta doesn't affect Google Ads, TikTok, LinkedIn, Microsoft Ads, or any CRM, payment, form, meeting, or chat integration. --- # TikTok Ads Conversion Tracking & Attribution Setup Guide Connect TikTok Ads to push offline conversions back to your Pixel via the Events API. Multi-advertiser support, hashed PII matching, ttclid tracking. Source: https://sourceloop.ai/help/connect-tiktok-ads/ Updated: 2026-05-28 --- Connecting TikTok Ads to SourceLoop opens the full conversion loop. Every tracked lead converts (form submission, meeting booked, payment received), and SourceLoop sends that event server-side to TikTok via the Events API. The Smart Performance Campaigns and Value-Based Optimisation algorithms train on real revenue, not just browser-side Pixel events that get blocked by privacy settings. This article covers the OAuth connect flow only. After connecting, see: - [Configure TikTok Events API sync](/help/configure-tiktok-events-api/) - [Troubleshoot TikTok Ads sync issues](/help/troubleshoot-tiktok-ads-sync/) ## Why connect TikTok Ads to SourceLoop? TikTok Pixel events fired in browsers cover maybe 60-70% of conversions on a good day. Mobile in-app browsers, aggressive privacy settings, and ad blockers eat the rest. The Events API closes that gap by sending events server-side from SourceLoop instead of the browser. What SourceLoop adds: - **Server-side conversion push** via the TikTok Events API, immune to browser-side blocking - **Click ID matching** using `ttclid` plus hashed email and phone - **Revenue values** sent on every conversion when payment integrations are connected - **Event-level dedup** so server-side and browser-side reporting of the same conversion don't double-count - **Multi-touch attribution** within SourceLoop, while still feeding TikTok the conversion signals needed for VBO and Smart bidding ## Before you start You'll need: - A **SourceLoop workspace** with the [tracking pixel installed](/help/install-the-tracking-pixel/) - A **TikTok Business Center** account that owns the advertisers you want to connect - A TikTok user with **Admin** or **Standard** access on the Business Center - An existing **TikTok Pixel** in Events Manager (or a Server Pixel created specifically for CAPI) - **Admin** or **Owner** role in SourceLoop (Editors can't add integrations) ## Step 1: Open the Ad Platforms page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Setup** in the left sidebar. 3. Click the **Ad Platforms** tab. 4. Scroll to the **TikTok Ads** card and click **Connect**. You'll be redirected to TikTok's OAuth consent screen on business-api.tiktok.com. ## Step 2: Authorise SourceLoop on TikTok 1. Sign in with the TikTok user that has Business Center access. 2. Pick the **Business Center** that owns the advertisers you want to connect. 3. Review the access SourceLoop is requesting (read and write to advertiser data, plus pixel events). 4. Click **Confirm**. TikTok redirects you back to SourceLoop with an authorisation code, which SourceLoop exchanges for an access token. The token is stored encrypted. ## Step 3: Pick the advertiser If your TikTok user has access to more than one advertiser, SourceLoop shows a picker listing each one, including the currency and timezone. 1. Find the advertiser you want to connect. 2. Click **Connect this advertiser**. SourceLoop creates a connection for that specific advertiser. The TikTok Ads card flips to **Connected** with the advertiser name and Advertiser ID. > **To connect another advertiser, repeat the flow** > Each TikTok advertiser is its own connection. To connect multiple advertisers (e.g., one per brand or region), run the Connect TikTok Ads flow once per advertiser. They sync and push conversions independently. ## Step 4: Add your Pixel ID and Events API token For SourceLoop to actually push conversions, it needs your **Pixel ID** and an **Events API access token** that's authorised to write to that Pixel. The full walk-through is in [Configure TikTok Events API sync](/help/configure-tiktok-events-api/). Short version: 1. In SourceLoop's TikTok drawer, find the **CAPI configuration** section. 2. Paste your **Pixel ID** (from TikTok Events Manager). 3. Paste your **Events API access token** (generated from the Pixel's Settings tab). 4. Click **Save**. The connection status flips to **Active**, and the daily Insights sync + the 2-minute Events API push cycle both start running. ## What gets synced Once connected, SourceLoop runs two flows automatically: **PULL (Insights sync, daily at 05:00 UTC):** - Campaign, ad group, and ad hierarchy - Daily spend, impressions, clicks per level - 14-day rolling re-sync to catch TikTok's late attribution - Refreshed in SourceLoop dashboards within minutes of each sync run **PUSH (Events API, every 2 minutes):** - Every SourceLoop conversion where the visitor's session carries a `ttclid` cookie or a hashed email / phone that matches a TikTok user - Conversion value (currency) and currency code when configured - Stable event IDs for dedup against any browser-side Pixel events fired on the same action - Page URL where the conversion happened ## What's next - **Pick which SourceLoop events map to which TikTok event name:** [Configure TikTok Events API sync](/help/configure-tiktok-events-api/). - **Troubleshoot** any push errors: [Troubleshoot TikTok Ads sync issues](/help/troubleshoot-tiktok-ads-sync/). - **Disconnect or reset** the integration: [Disconnect TikTok Ads from SourceLoop](/help/disconnect-tiktok-ads-from-sourceloop/). ## Frequently Asked Questions ### Do I need a TikTok Business Center account? Yes. SourceLoop connects to advertisers (TikTok's term for ad accounts) inside a Business Center. If you've been running TikTok Ads via a personal account, claim it inside a Business Center at business.tiktok.com first. ### Which advertisers can I connect? Every active advertiser your TikTok user has access to inside the Business Center. After OAuth, SourceLoop shows a picker (with currency and timezone) and you pick one per connection. Repeat to connect multiple. ### What's the difference between the TikTok Pixel ID and the Events API access token? The Pixel ID identifies the Pixel (or Server Pixel) that should receive the events. The Events API access token is the credential SourceLoop uses to call the API against that Pixel. Both are configured in the TikTok drawer after the initial OAuth, the next article walks through finding each. ### How long does the TikTok OAuth access last? The OAuth access token issued during connect is long-lived and SourceLoop reuses it for both Insights sync and Events API push. If TikTok ever revokes the token (rare, usually only happens if a user explicitly removes the app from Business Center), the card flips to Token expired and you'll need to reconnect. ### Can I sync TikTok Shop conversions? Yes, if they're configured as TikTok Pixel events. The Events API push doesn't distinguish between TikTok Shop and other conversion types; any event you map in SourceLoop gets sent to TikTok against the Pixel you've configured. ### Does TikTok have something equivalent to Meta's Match Quality? TikTok's diagnostic equivalent is the Event Match Score, visible in TikTok Events Manager. Like Meta's Match Quality, it improves with richer identity signals, ttclid + email + phone produces the best scores. The improvements you make to Meta CAPI matching usually carry over to TikTok automatically. ### Can I connect multiple TikTok advertisers to one SourceLoop workspace? Yes. Each advertiser is a separate connection. Repeat the Connect TikTok Ads flow once per account. --- # How to Sync Revenue and Offline Conversions in TikTok Ads Set up TikTok Events API push from SourceLoop. Find your Pixel ID, generate an access token, map SourceLoop events to TikTok event names, and verify. Source: https://sourceloop.ai/help/configure-tiktok-events-api/ Updated: 2026-05-28 --- After you've [connected TikTok Ads to SourceLoop](/help/connect-tiktok-ads/), the next step is wiring up the Events API push. You'll find a Pixel ID and access token in TikTok Events Manager, paste both into SourceLoop, then map which SourceLoop events get sent to which TikTok event name with what value. ## Before you start You'll need: - [TikTok Ads connected to SourceLoop](/help/connect-tiktok-ads/) (the card shows the advertiser name) - Access to **TikTok Ads Manager** for the advertiser (specifically Events Manager / Assets -> Events) - The Pixel you'll be reporting to (a regular Pixel, or a Server Pixel created specifically for CAPI) - A list of SourceLoop conversions you want to push and what TikTok event name each maps to (`PlaceAnOrder`, `SubmitForm`, `Subscribe`, etc.) ## Step 1: Find your TikTok Pixel ID 1. Sign in to [TikTok Ads Manager](https://ads.tiktok.com/) with a Business Center user. 2. Open **Assets -> Events** in the left sidebar. 3. Click into the **Pixel** (or **Server Pixel**) you want to report to. 4. On the Pixel's overview / settings page, find the **Pixel ID** at the top, a 20-character alphanumeric string. 5. Copy it. ## Step 2: Generate the Events API access token 1. Stay on the Pixel's settings page in Events Manager. 2. Scroll to the **Events API** section (sometimes labeled "Manage" or "Set up"). 3. Click **Generate Access Token** (or **Manage Access Token** if you've already generated one). 4. Copy the token shown. **TikTok only shows it once, save it somewhere safe.** If you ever lose the token, you can revoke and generate a new one from the same screen. SourceLoop accepts either; just re-paste in the next step. ## Step 3: Paste both values into SourceLoop 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> TikTok Ads**. 3. In the **CAPI configuration** section of the drawer: - Paste the **Pixel ID** from Step 1 - Paste the **Events API access token** from Step 2 - (Optional) Paste a **Test event code** for end-to-end testing 4. Click **Save**. SourceLoop validates the token against TikTok's API. If valid, the connection status flips to **Active**. ## Step 4: Add your conversion sync mappings Now wire up which SourceLoop events get pushed to which TikTok event name. 1. In the TikTok drawer, scroll to **Conversion sync**. 2. Click **Add conversion mapping**. 3. Pick the **Trigger event type**: - **Conversion created** — every new lead conversion (form, meeting, chat with email captured, first payment) - **Expected revenue updated** — when the lead's expected revenue changes (CRM-driven) - **Realized revenue updated** — when a payment integration confirms revenue 4. (Optional) Set a **Trigger event name filter** to scope to a specific source. 5. Pick an **Attribution model**: **Last touch** or **First touch**. 6. Pick the **TikTok event name**: - **PlaceAnOrder** — TikTok's equivalent of Purchase for ecommerce - **CompletePayment** — when payment is finalised - **SubmitForm** — most common for forms / lead capture - **Subscribe** — for new SaaS subscriptions - **CompleteRegistration** — for account creation - **Lead**, **Contact** — lead-generation events - **AddToCart**, **InitiateCheckout** — funnel events - **Custom event name** — type any string for a custom event 7. Toggle **Include value** if you want to send a revenue figure. Pick the value source (`quote_value`, `sales_value`, or fixed amount) and currency. 8. Set the **dedup window** in minutes (default 1440). 9. Click **Save**. SourceLoop starts pushing matching conversions on the next 2-minute flush cycle. > **Start with a test_event_code** > Set a test code in Events Manager's Test events tab and paste it in SourceLoop's CAPI configuration. Fire a real conversion (incognito, with utm params, complete the form / checkout), and watch it appear in Events Manager's Test events view within seconds. Confirm Event Match Score is acceptable, then clear the test code so events flow to production. ## What gets sent in each event For every matching SourceLoop conversion, the push to TikTok includes: - **event** — the event name from your mapping (e.g., `PlaceAnOrder`, `SubmitForm`) - **event_id** — a stable identifier per SourceLoop conversion, used by TikTok to dedup against browser-side Pixel events - **timestamp** — when the conversion happened (ISO 8601) - **context.page.url** — the landing page where the conversion happened - **context.user** — matchable signals SourceLoop has captured: - `ttclid` (TikTok click ID) when present - hashed email (SHA256) - hashed phone (SHA256) - external_id (SourceLoop's contact ID for dedup) - **properties** — `value` and `currency` when value is included The richer the user data, the better TikTok's Event Match Score. `ttclid` alone gives strong matches; email + phone alone gives medium matches; both together produces near-perfect Match Score. ## What's next - **Troubleshoot pushes that aren't appearing:** [Troubleshoot TikTok Ads sync issues](/help/troubleshoot-tiktok-ads-sync/). - **Add more mappings** for separate funnels: repeat Step 4 with each TikTok event name. ## Frequently Asked Questions ### Where do I find my TikTok Pixel ID? In TikTok Ads Manager, open Assets -> Events. Pick the Pixel (or Server Pixel) you want to report to. The Pixel ID is shown at the top of the Pixel's settings page, a 20-character alphanumeric string. Copy it as-is. ### Where do I generate the Events API access token? In the same Pixel's Settings tab, scroll to the Events API section and click Generate Access Token (or Manage Access Token if you've already generated one). The token is shown once, copy it immediately. Save it somewhere safe. ### Can I use the same token for multiple Pixels? No. Each Pixel has its own Events API token, scoped to that Pixel only. If you have multiple Pixels (e.g., one per brand), generate a separate token per Pixel and create a separate SourceLoop connection per advertiser. ### What event names does TikTok accept? TikTok has a standard event list, ViewContent, ClickButton, AddToWishlist, AddToCart, InitiateCheckout, AddPaymentInfo, PlaceAnOrder (their equivalent of Purchase), CompletePayment, Subscribe, CompleteRegistration, Contact, SubmitForm, Search, Download. You can also send a Custom event by typing any name. Lead is also accepted but mapped internally by TikTok. ### How does dedup work with TikTok? SourceLoop generates a stable event_id per conversion. TikTok dedups within a 24-hour window when the same event_id arrives from both the browser-side Pixel and the Events API. Most teams either CAPI-only (cleanest) or align the event_ids in their Pixel tags. ### I see test_event_code in SourceLoop. How do I use it? Open TikTok Events Manager, pick your Pixel, click the Test Events tab. Copy the test code shown. Paste it in SourceLoop's CAPI configuration. Events sent with that code are routed to Test Events only, not your production Pixel feed, useful for end-to-end testing without polluting real campaign data. ### What's the difference between a regular Pixel and a Server Pixel? A Server Pixel is TikTok's purpose-built Events API endpoint, designed for server-side reporting only (no browser-side script). SourceLoop works with both, the integration is identical. If you don't have any browser-side TikTok Pixel installed, create a Server Pixel for cleaner ownership of CAPI-only data. --- # How to troubleshoot TikTok Ads sync issues Checklist for TikTok Ads sync problems. Pixel ID errors, Events API token expiry, missing CAPI events, Event Match Score, advertiser permission errors. Source: https://sourceloop.ai/help/troubleshoot-tiktok-ads-sync/ Updated: 2026-05-28 --- TikTok Ads sync issues fall into a few buckets. Work through this checklist in order. ## Before you start Have these tabs open: - **SourceLoop's TikTok Ads card** at **Setup -> Ad Platforms -> TikTok Ads** - **TikTok Events Manager** for the Pixel you're reporting to - The most recent **Sync log** entry on the TikTok card ## Step 1: Check the connection status 1. Open **Setup -> Ad Platforms -> TikTok Ads** in SourceLoop. 2. Look at the card: - **Active** with recent Last sync → healthy - **Active** with stale Last sync (>24 hours) → Insights sync stuck - **Token expired** → reconnect needed - **Disconnected** → run Connect flow ## Step 2: For 'Token expired' / 'Disconnected' 1. Click **Reconnect** on the TikTok card. 2. Sign in to TikTok with the user that has Business Center access. 3. Authorise the scopes. 4. Pick the same advertiser in the picker. Also check that your **Events API access token** (in the CAPI configuration section) is still valid. If it was rotated or revoked, generate a fresh one in Events Manager and paste it into SourceLoop. ## Step 3: For 'conversions not appearing in Events Manager' This is the most common issue. Walk through: 1. **Open the Sync log** on the TikTok card (three-dot menu). 2. Find the conversion you expected. Each push is logged with the event_name, Pixel ID, value, and status: - **sent** → SourceLoop called the Events API successfully. The event may take 30-90 seconds to appear in Events Manager. - **pending_identity** → no usable identity signal (no ttclid, no email, no phone). Skip these. - **error** → TikTok rejected the push. Click the row for the exact error. Common errors: - **`code 40001` / `Access token not valid`** → token expired or rotated. Regenerate from Events Manager and update SourceLoop. - **`Pixel not found`** → wrong Pixel ID in CAPI configuration. - **`Permission denied`** → token was issued for a different Pixel. Each Pixel has its own token. - **`Event name not supported`** → custom event name has unsupported characters. Use camelCase or underscored names. ## Step 4: For test events not showing in the Test Events tab If you set a test_event_code but events aren't showing: 1. Confirm the **test_event_code in SourceLoop matches** the one in Events Manager Test Events tab. They must be identical. 2. The code is **per Pixel**, look at the Test Events tab for the Pixel ID you configured in SourceLoop. 3. After firing a conversion, **refresh the Test Events tab manually**. 4. Confirm the conversion **actually fired in SourceLoop** by checking the Contacts Hub for a recent entry. ## Step 5: For poor Event Match Score Match Score is TikTok's measure of how well CAPI events match to real TikTok users. To improve it: 1. **Install the SourceLoop tracking pixel on every landing page**, especially TikTok ad landing pages. ttclid is set on first ad-click landing; if the page doesn't load the SourceLoop tracker, the cookie never gets captured. 2. **Capture email and phone in your forms.** TikTok hashes them client-side before sending and uses the hashes to match against real users. 3. **Don't push events for anonymous visitors.** SourceLoop skips visitors with no identity signal (status `pending_identity`). Configure your conversion sources to require email at minimum. 4. **Combine signals.** ttclid alone gives strong matches; email + phone alone gives medium matches; both together produces near-perfect Match Score. ## Step 6: For Insights data not updating 1. **Check the last sync timestamp** on the TikTok card. Daily at 05:00 UTC. 2. **Force a manual resync.** Click **Resync now**. 3. **Check TikTok's own reporting.** If TikTok Ads Manager shows the same lag (common for the last 1-3 days), the data isn't ready yet on TikTok's end. ## How to disconnect or reset - **Soft reset** (re-run click-mapping backfill): three-dot menu -> **Reset click mapping**. - **Hard reset** (disconnect entirely): see [Disconnect TikTok Ads from SourceLoop](/help/disconnect-tiktok-ads-from-sourceloop/). ## When to email support If you've worked through the checklist and CAPI events still aren't appearing, email **hello@sourceloop.ai** with: - The TikTok Ads card's current status - Two or three error messages from the Sync log (verbatim) - Your **Advertiser ID** (visible on the TikTok card) - Your **Pixel ID** We respond within one business day. ## Frequently Asked Questions ### My TikTok card shows 'Token expired'. What now? Reconnect from the TikTok card to run a fresh OAuth flow. TikTok access tokens are typically long-lived; a 'Token expired' state usually means the app was removed from Business Center or the OAuth grant was revoked. After reconnecting, also confirm the Events API access token in the CAPI configuration is still valid. ### Conversions are firing in SourceLoop but not in TikTok Events Manager. What should I check? Five common causes. (1) Test event code set, events go to Test events only, not the live Pixel feed. Remove the test code. (2) Pixel ID wrong, double-check it matches the Pixel in Events Manager exactly. (3) Events API access token expired, regenerate from the Pixel's Settings tab. (4) Token issued for a different Pixel, each Pixel has its own token. (5) Events take 30-90 seconds to appear in Events Manager UI, that's normal. ### TikTok says 'Access token not valid' or 'code 40001' in the sync log. Why? The Events API access token has been rotated or revoked. Generate a fresh one from Events Manager -> the Pixel -> Settings -> Events API section, and update SourceLoop's CAPI configuration. ### My Event Match Score is poor. How do I improve it? Make sure SourceLoop is capturing as much identity signal as possible. Install the tracking pixel on every landing page (so ttclid gets captured early), capture email and phone in your forms (TikTok hashes them for matching), and don't push events for visitors with no identity. Forms requiring email at minimum produce significantly better Match Scores. ### I'm getting 'code 40001' / 'permission denied'. What does it mean? The Events API access token doesn't have permission to write to the Pixel ID you specified. Either the token was generated for a different Pixel, or the token has been revoked. Regenerate from the right Pixel. ### How do I send a test event? Open TikTok Events Manager, pick your Pixel, click the Test Events tab. Copy the test code shown. Paste it in SourceLoop's TikTok drawer in the CAPI configuration. Fire a real conversion on your site, the event should appear in Test Events within 30-60 seconds. ### Insights data is stale. When does it refresh? Insights sync runs daily at 05:00 UTC and re-fetches the last 14 days. Force an immediate refresh by clicking Resync now. Recent campaign performance (last 1-3 days) often lags in TikTok itself, you're not behind, TikTok is. --- # How to disconnect TikTok Ads from SourceLoop Disconnect SourceLoop from your TikTok Business Center. What stops, what is deleted immediately, what clears in 30 days, and how to confirm removal. Source: https://sourceloop.ai/help/disconnect-tiktok-ads-from-sourceloop/ Updated: 2026-05-28 --- This article covers the TikTok Ads disconnect flow end-to-end, what stops, what gets deleted, and how to confirm full removal. It complies with TikTok's [Developer Terms of Service](https://ads.tiktok.com/marketing_api/docs?id=1738373164380161) including data deletion requirements for Events API integrations. ## Before you start Set expectations: - **Disconnecting on SourceLoop's side** revokes your TikTok authorization, wipes the Pixel ID and Events API token, and clears all configuration, all immediately. - **Historical campaign performance data** is deleted asynchronously within 30 days. - **Conversions already received by TikTok's Events API stay in TikTok.** ## Step 1: Disconnect on SourceLoop's side 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> TikTok Ads**. 3. On the TikTok Ads card, click the three-dot menu (top-right) and select **Disconnect**. 4. Confirm the disconnect dialog. The moment you confirm, SourceLoop runs the full disconnect. Here's what happens: 1. **Your TikTok authorization is revoked.** SourceLoop tells TikTok to invalidate the access we were granted, and we wipe our own copy at the same time. 2. **Your Pixel ID and Events API token are cleared from SourceLoop.** The Pixel stays in TikTok Events Manager (untouched). The Events API access token stays valid in TikTok until you choose to rotate it. 3. **Your conversion sync settings are cleared.** Every conversion mapping, value source, event name choice, attribution model, and dedup rule is removed. 4. **Pending conversion pushes are cancelled.** Anything queued to be sent is dropped. 5. **Your campaign performance history is queued for deletion.** Spend, impressions, clicks, and the click-to-ad lookups used for attribution are removed asynchronously, usually within minutes, always within 30 days. 6. The TikTok Ads card on the Ad Platforms tab flips to **Disconnected**. ## Step 2: Remove SourceLoop from TikTok Business Center (recommended) This step is optional, Step 1 already revokes the OAuth grant. For clean audit trails: 1. Sign in to [TikTok Business Center](https://business.tiktok.com/). 2. Open **Settings** for the Business Center that has SourceLoop installed. 3. Find **Apps** or **Connected Apps**. 4. Find **SourceLoop** in the list. 5. Click **Remove** and confirm. TikTok logs the removal in your Business Center audit trail. ## Step 3: Decide what to do with your Pixel and access token SourceLoop doesn't create or modify Pixels, Events API tokens, or events on TikTok's side. So there's nothing on TikTok's end for SourceLoop to clean up. But you can: - **Keep the Pixel** as-is. It continues to receive browser-side events and any other Events API integrations. - **Rotate the Events API access token** if you don't plan to reconnect. Go to Events Manager -> Pixel -> Settings -> Events API and rotate. Belt-and-braces, the access has already been revoked from SourceLoop's side. ## What SourceLoop holds, and what gets deleted ### Deleted immediately at disconnect These go away the moment you click Disconnect: - **Your TikTok authorization** (revoked on TikTok's side and removed from ours) - **Your Pixel ID and Events API token** as configured in SourceLoop (the Pixel itself in TikTok is untouched) - **The connection itself**, including the Advertiser ID, account name, currency, and timezone - **All your conversion sync settings**, event name mappings, value sources, attribution choices, dedup windows - **All pending conversion pushes** that hadn't yet been sent - **Your sync run history** for this connection ### Deleted within 30 days These are queued for asynchronous deletion. Most clear within minutes; our privacy commitment guarantees completion within 30 days: - **Your campaign performance history in SourceLoop** (spend, impressions, clicks per campaign / ad group / ad, collected during your connection) - **The click-to-ad lookups** used for attribution ### Never held in the first place For transparency, SourceLoop also does not, at any point, access: - Your TikTok personal content, followers, or any non-advertising data - Audience segments inside your TikTok ad account beyond what's needed to push conversion events ## Confirming the deletion For compliance audits: 1. Email **hello@sourceloop.ai** with the subject "Confirm TikTok Ads deletion" and your TikTok Advertiser ID. 2. We reply within 2 business days with deletion timestamps for each data category. Suitable for SOC2, ISO27001, and GDPR Article 17 audit trails. ## Reconnecting later You can reconnect at any time. Disconnect wipes the connection and its settings, so reconnect starts fresh: 1. Sign in to SourceLoop. 2. Open **Setup -> Ad Platforms -> TikTok Ads**. 3. Click **Connect**. 4. Run the OAuth flow. 5. Pick the same advertiser. 6. **Re-enter your Pixel ID and a fresh Events API access token.** 7. **Reconfigure your conversion sync mappings.** ## Privacy and security For full transparency: - **What we ask for:** access to the TikTok advertisers you authorise. Nothing else. - **How we store your authorization:** encrypted at rest with industry-standard practices. The raw access token never appears in logs or in our UI. - **Events API token:** encrypted at rest; we never display it in plaintext after save. You can rotate it any time on TikTok's side without notifying us. - **Third parties:** SourceLoop does not sell, transfer, or share TikTok-sourced data with anyone outside SourceLoop. - **TikTok Developer Terms:** SourceLoop complies with TikTok's [Developer Terms of Service](https://ads.tiktok.com/marketing_api/docs?id=1738373164380161) and Events API requirements. - **Encryption in transit:** all communication with TikTok's APIs uses TLS 1.2+. ## When to email support For anything outside the standard flow: - **"I disconnected by mistake"** → email hello@sourceloop.ai - **"Our compliance team needs a written DPA"** → we provide a signed DPA on request - **"An auditor needs evidence of deletion"** → we provide written confirmation keyed to your Advertiser ID Email **hello@sourceloop.ai** for any of the above. We respond within one business day. ## Frequently Asked Questions ### How quickly does SourceLoop stop pushing conversions after I disconnect? Immediately. The moment you confirm Disconnect, SourceLoop revokes your TikTok authorization, wipes the Pixel ID and Events API token from our system, and clears the connection. The 2-minute Events API push cycle queries active connections only, so the very next cycle excludes yours. ### Does disconnecting delete events I've already pushed to TikTok? No. Events already received by the TikTok Events API stay in TikTok. The bidding signals trained on those events continue to inform optimisation. SourceLoop cannot retroactively remove events from TikTok's pipeline. ### What happens to the Pixel ID and Events API token I configured? Both are wiped from SourceLoop the moment you disconnect. The Pixel itself stays in TikTok Events Manager, untouched. The Events API access token stays valid in TikTok until you choose to rotate it. ### What gets deleted immediately vs within 30 days? Deleted immediately, your TikTok authorization, the Pixel ID and Events API token stored in SourceLoop, all conversion sync settings, sync history, and any pending pushes. Deleted within 30 days, your campaign performance history in SourceLoop (spend, impressions, clicks per campaign / ad group / ad) and the click-to-ad lookups used for attribution. ### How do I verify the deletion? Email hello@sourceloop.ai with the subject "Confirm TikTok Ads deletion" and your TikTok Advertiser ID. We provide a written confirmation with deletion timestamps for each data category, suitable for compliance audits. ### Will reconnecting later restore my settings? No. Disconnect wipes everything tied to the connection. Reconnecting starts fresh, you'll re-enter the Pixel ID, paste a fresh Events API access token, and reconfigure conversion sync mappings. ### Will disconnecting TikTok affect my other ad platforms? No. Each ad platform connection is independent. --- # LinkedIn Ads Conversion Tracking & Attribution Setup Guide Connect LinkedIn Ads to push offline conversions via the Conversions API and capture Lead Gen Forms in real time. Multi ad-account, li_fat_id. Source: https://sourceloop.ai/help/connect-linkedin-ads/ Updated: 2026-05-28 --- Connecting LinkedIn Ads to SourceLoop opens the full conversion loop for B2B campaigns. Every tracked lead converts (form submit, meeting booked, payment received), and SourceLoop sends that event server-side to LinkedIn via the Conversions API. Your bidding strategies and reporting train on real revenue instead of relying only on the Insight Tag's browser-side coverage. LinkedIn Ads is one of the highest-CPC ad platforms, so offline conversion sync matters more here than anywhere else. Without it, you're optimising against vanity clicks. With it, your CPL and ROAS reports tell the truth. This article covers the OAuth connect flow only. After connecting, see: - [Configure LinkedIn Conversions API sync](/help/configure-linkedin-offline-conversions/) - [Capture LinkedIn Lead Gen Form submissions](/help/capture-linkedin-lead-gen-forms/) (for accounts running Lead Ads) - [Troubleshoot LinkedIn Ads sync issues](/help/troubleshoot-linkedin-ads-sync/) ## Why connect LinkedIn Ads to SourceLoop? LinkedIn's Insight Tag fires browser-side, which means it covers maybe 40-60% of conversions on a B2B audience (which is heavier on iOS Safari, corporate VPNs, ad blockers, and consent banners than consumer traffic). The Conversions API fills in the rest from server-side, so: - **Server-side conversion push** via the LinkedIn Conversions API, immune to browser-side blocking - **Identity matching** using `li_fat_id` (LinkedIn First-Party Ads Tracking) plus hashed email and phone - **Conversion values** sent with every event when payment integrations are connected - **Lead Gen Form capture** in real time via webhook (separate setup, see linked article) - **Multi-touch attribution** within SourceLoop, while feeding LinkedIn the conversion data needed for tCPA-style bidding ## Before you start You'll need: - A **SourceLoop workspace** with the [tracking pixel installed](/help/install-the-tracking-pixel/) - A **LinkedIn user** with Campaign Manager access on the ad accounts you want to connect (Account Manager or higher) - A **LinkedIn Insight Tag** installed on your site (recommended, helps populate `li_fat_id` for matching) - An existing **Conversion Rule** in LinkedIn Campaign Manager (you'll get its Conversion Rule ID in the next article) - **Admin** or **Owner** role in SourceLoop ## Step 1: Open the Ad Platforms page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Setup** in the left sidebar. 3. Click the **Ad Platforms** tab. 4. Scroll to the **LinkedIn Ads** card and click **Connect**. You'll be redirected to LinkedIn's OAuth consent screen. ## Step 2: Authorise SourceLoop on LinkedIn 1. Sign in to LinkedIn with the user that has Campaign Manager access. 2. Review the scopes SourceLoop is requesting (read/write on ads, ads reporting access). 3. Click **Allow**. LinkedIn redirects you back to SourceLoop with an authorisation code, which SourceLoop exchanges for an access token + refresh token. Both are stored encrypted. Access tokens live for 60 days and rotate automatically; refresh tokens last longer. ## Step 3: Pick the ad account If your LinkedIn user has access to more than one ad account (common for agencies and teams), SourceLoop shows a picker listing each active account with its currency. 1. Find the ad account you want to connect. 2. Click **Connect this account**. SourceLoop creates a connection for that specific ad account. The LinkedIn Ads card flips to **Connected** with the account name and Account ID. > **To connect another account, repeat the flow** > Each LinkedIn ad account is its own connection. Run the Connect LinkedIn Ads flow once per account; they sync and push conversions independently. ## Step 4: Pick what to sync and where conversions go After connecting, the LinkedIn drawer opens with sync settings: 1. **Inbound sync** (LinkedIn → SourceLoop): pulls campaign / ad group / ad hierarchy plus daily spend and impressions, enabled by default. 2. **Outbound sync** (SourceLoop → LinkedIn): pushes conversions via the Conversions API, enabled by default once you add a conversion mapping. 3. **Lead Gen Form capture** (separate flow): an optional add-on for accounts running Lead Ads. See [Capture LinkedIn Lead Gen Form submissions](/help/capture-linkedin-lead-gen-forms/). For the Conversions API push to actually work, you need to add at least one conversion mapping with a Conversion Rule ID, see [Configure LinkedIn Conversions API sync](/help/configure-linkedin-offline-conversions/). ## What gets synced Once connected and configured, SourceLoop runs two flows automatically: **PULL (Insights sync, daily at 05:00 UTC):** - Campaign, campaign group, and ad / creative hierarchy - Daily spend, impressions, clicks per level - 14-day rolling re-sync to catch LinkedIn's late attribution - Refreshed in SourceLoop dashboards within minutes of each sync run **PUSH (Conversions API, every 2 minutes):** - Every SourceLoop conversion where the visitor's session carries an `li_fat_id` cookie or a hashed email / phone that matches a LinkedIn user - Conversion value (currency) and currency code when configured - Mapped to your LinkedIn Conversion Rule URN ## What's next - **Pick which SourceLoop events get pushed to which LinkedIn conversion rule:** [Configure LinkedIn Conversions API sync](/help/configure-linkedin-offline-conversions/). - **Capture Lead Gen Form submissions** in real time: [Capture LinkedIn Lead Gen Form submissions](/help/capture-linkedin-lead-gen-forms/). - **Troubleshoot** any push errors: [Troubleshoot LinkedIn Ads sync issues](/help/troubleshoot-linkedin-ads-sync/). - **Disconnect or reset** the integration: [Disconnect LinkedIn Ads from SourceLoop](/help/disconnect-linkedin-ads-from-sourceloop/). ## Frequently Asked Questions ### Do I need a specific LinkedIn ad account tier? No. Any LinkedIn ad account works, Self-service or managed. The integration uses the standard Marketing API and Conversions API endpoints which are available on every active account. ### Which ad accounts can I connect? Every active ad account your LinkedIn user has access to. After OAuth, SourceLoop shows the list (up to 20) and you pick one per connection. Repeat the flow to connect multiple ad accounts. ### What scopes does SourceLoop request? The required scopes are r_ads, rw_ads, r_ads_reporting. If you also enable Lead Gen Form capture (a separate step after connect), SourceLoop requests an additional scope for Lead Sync. We never ask for personal LinkedIn content like messages, connections, or profile data beyond your name and email used to label the connection. ### How long does the LinkedIn token last? 60 days. SourceLoop stores the refresh token and rotates the access token automatically as needed, you won't need to reconnect unless someone explicitly revokes SourceLoop's access on LinkedIn's side. ### Does this work for sponsored content, Message Ads, and Conversation Ads? Yes. The Conversions API push is campaign-type-agnostic. As long as your visitors have the LinkedIn Insight Tag (or li_fat_id click cookie) when they arrive, SourceLoop can match the conversion back to the originating campaign regardless of format. ### Can I connect multiple LinkedIn ad accounts to one SourceLoop workspace? Yes. Each ad account is a separate connection. Repeat the Connect LinkedIn Ads flow once per account. ### What's the difference between this and LinkedIn's built-in Conversion Tracking? LinkedIn's built-in tracking depends on browser-side Insight Tag firing. Privacy settings, browser extensions, and the LinkedIn iOS app block much of this. SourceLoop's Conversions API integration sends conversions server-side so they get through regardless. The two can run side-by-side, LinkedIn dedupes overlapping events via conversion rule + timestamp. --- # How to Sync Revenue and Offline Conversions in LinkedIn Ads Set up LinkedIn Conversions API push by creating a Conversion Rule in Campaign Manager, finding its ID, and mapping SourceLoop events to it. Source: https://sourceloop.ai/help/configure-linkedin-offline-conversions/ Updated: 2026-05-28 --- After you've [connected LinkedIn Ads to SourceLoop](/help/connect-linkedin-ads/), the next step is wiring up the Conversions API push. You'll create a Conversion Rule in LinkedIn Campaign Manager, find its ID, and map SourceLoop events to it. This is the step that turns clicks into measurable B2B revenue. LinkedIn Ads spend with no conversion sync optimises against vanity clicks; with the Conversions API connected, it optimises against real qualified leads. ## Before you start You'll need: - [LinkedIn Ads connected to SourceLoop](/help/connect-linkedin-ads/) - Access to **LinkedIn Campaign Manager** for the ad account you connected (Account Manager role or higher) - A clear idea of which SourceLoop events you want to track in LinkedIn (typically: Lead, Demo Booked, Paid Signup) - **Admin** or **Owner** role in SourceLoop ## Step 1: Create the Conversion Rule in Campaign Manager If you already have a Conversion Rule set up, skip to Step 2. 1. Sign in to [Campaign Manager](https://www.linkedin.com/campaignmanager/). 2. Pick the ad account you connected to SourceLoop. 3. Go to **Analyze -> Conversion Tracking -> Conversions**. 4. Click **+ Create conversion**. 5. Pick **Offline conversion** as the source. 6. Choose the **Conversion type** that matches your funnel stage: - **Lead** — most common for form fills / meetings / chat with email captured - **Sign up** — new account registrations - **Subscribe** — new SaaS subscriptions - **Purchase** — one-off product sales - **Other** — anything custom 7. Name it something descriptive (e.g., "SourceLoop, Demo Booked"). 8. Set the conversion window (default 30 days, the maximum LinkedIn allows). 9. Set the conversion value if you want a default value (you'll override per-conversion in SourceLoop). 10. Save the rule. Repeat for each separate funnel stage you want to track. ## Step 2: Find the Conversion Rule ID 1. In Campaign Manager, open the Conversion Rule you just created (or any existing rule you want SourceLoop to push to). 2. Look at the URL in your browser, it ends in something like `/conversion/12345678/`. The number is the **Conversion Rule ID**. 3. Alternatively, the ID is also visible in the rule's settings panel. 4. Copy the numeric ID, you'll paste it into SourceLoop in the next step. ## Step 3: Add the conversion mapping in SourceLoop 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> LinkedIn Ads**. 3. Scroll to **Conversion sync** in the drawer. 4. Click **Add conversion mapping**. 5. Pick the **Trigger event type**: - **Conversion created** — every new lead conversion (form, meeting, chat with email, initial payment) - **Expected revenue updated** — when the lead's expected revenue changes - **Realized revenue updated** — when a payment integration confirms revenue 6. (Optional) Set a **Trigger event name filter** to scope this mapping to a specific source. 7. Pick an **Attribution model**: **Last touch** or **First touch**. 8. Paste the **Conversion Rule ID** from Step 2 (just the numeric ID, e.g., `12345678`). 9. Toggle **Include value** if you want to send a revenue figure. Pick the value source (`quote_value`, `sales_value`, or fixed) and currency. 10. Set the **dedup window** in minutes (default 1440). 11. Click **Save**. SourceLoop starts pushing matching conversions on the next 2-minute flush cycle. Campaign Manager will show the conversion counts within 4-24 hours. > **Create separate Conversion Rules per funnel stage** > LinkedIn groups conversions by rule in Campaign Manager reports. If you want to see "Lead" separately from "Demo Booked" separately from "Paid Signup", create three Conversion Rules in Campaign Manager and map three corresponding configurations in SourceLoop. Single-rule setups lump everything together, which makes campaign-level optimisation harder. ## What gets sent in each event For every matching SourceLoop conversion, the push to LinkedIn includes: - **conversion** — the Conversion Rule URN (built from your Conversion Rule ID) - **conversionHappenedAt** — when the conversion happened (milliseconds since epoch) - **user.userIds** — the matchable signals SourceLoop has captured: - `LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID` (`li_fat_id`) when present - `SHA256_EMAIL`, hashed email - `SHA256_PHONE`, hashed phone - **conversionValue** — `amount` and `currencyCode` when value is included LinkedIn requires at least one identifier per event, so conversions for visitors with no `li_fat_id`, no email, and no phone are skipped. This is typically under 5% of conversions in practice, and the skipped ones are marked `pending_identity` in the Sync log so you can debug if needed. ## What's next - **Capture Lead Gen Form submissions in real time** (separate flow): [Capture LinkedIn Lead Gen Form submissions](/help/capture-linkedin-lead-gen-forms/). - **Troubleshoot pushes that aren't appearing:** [Troubleshoot LinkedIn Ads sync issues](/help/troubleshoot-linkedin-ads-sync/). - **Add more conversion mappings** for separate funnels: repeat Step 3 with each LinkedIn Conversion Rule ID. ## Frequently Asked Questions ### Where do I find the Conversion Rule ID? In LinkedIn Campaign Manager, go to Analyze -> Conversion Tracking -> Conversions. Click into the conversion rule you want SourceLoop to push to. The Conversion Rule ID is shown in the URL of the rule's detail page (`/conversion/{ID}/`) and on the rule's settings panel. It's a numeric ID, paste just the digits into SourceLoop. ### Do I need to create a separate Conversion Rule per SourceLoop event type? Yes. LinkedIn's reporting groups conversions by rule, so to see separate rows for Lead vs. Demo vs. Paid Signup, create a dedicated Conversion Rule for each. In SourceLoop, add one configuration row per (SourceLoop event, LinkedIn Conversion Rule) pair. ### What identity does LinkedIn use to match the conversion to a click? Three signals in order of accuracy. (1) li_fat_id, LinkedIn's First-Party Ads Tracking UUID, set by the Insight Tag on first click landing. (2) SHA256-hashed email. (3) SHA256-hashed phone. SourceLoop sends whichever it has captured. li_fat_id gives near-100% match; email or phone alone gives 60-80%; both together approaches 100%. ### Should I use the Insight Tag and the Conversions API together? Yes, that's the recommended setup. The Insight Tag captures browser-side conversions and helps populate li_fat_id. The Conversions API fills in the conversions that the tag missed (privacy-blocked, server-side workflows, after-the-fact CRM-confirmed deals). LinkedIn dedupes overlapping events automatically within the conversion rule's window. ### Can I send revenue values with conversions? Yes. In SourceLoop's conversion mapping, toggle Include value and pick the value source (quote_value, sales_value, or fixed amount). LinkedIn uses the value for ROAS and tCPA-style bidding. Currency defaults to USD if not set; specify a currency override if your ad account uses something else. ### My LinkedIn Conversion Rule shows 0 conversions. What should I check? Three causes. (1) No SourceLoop conversions have fired yet, check the Contacts Hub for recent entries. (2) Conversion Rule ID mismatch, double-check the digits in your SourceLoop config match the rule's URL in Campaign Manager. (3) The conversion is outside LinkedIn's lookback window (default 30 days), LinkedIn rejects older events. ### How long does it take for conversions to appear in Campaign Manager? Typically 4-24 hours after the first push. Campaign Manager batches conversion reporting and refreshes overnight. SourceLoop's Sync log will show the push completing within 2 minutes; LinkedIn just takes longer to surface it in their UI. --- # How to capture LinkedIn Lead Gen Form submissions Real-time webhook capture of LinkedIn Lead Gen Form submissions. Every Lead Ads response lands in SourceLoop with attribution and CRM sync. Source: https://sourceloop.ai/help/capture-linkedin-lead-gen-forms/ Updated: 2026-05-28 --- LinkedIn Lead Gen Forms are the highest-converting B2B ad format LinkedIn offers, and also the hardest to track. The lead never lands on your site, so your tracking pixel never sees them. Most teams resort to manual CSV exports or expensive Zapier flows. This article walks through the real-time webhook capture SourceLoop uses instead. Once configured, every Lead Gen Form submission flows into your workspace within seconds with full campaign attribution, ready to sync to your CRM. This is one of the highest-impact integrations in SourceLoop for B2B teams running LinkedIn Ads. ## Before you start You'll need: - [LinkedIn Ads connected to SourceLoop](/help/connect-linkedin-ads/) (the basic connect, runs first) - **Page Admin** access on each LinkedIn Page that owns Lead Gen Forms (not just Campaign Manager access, this is a Page-level permission) - At least one **published Lead Gen Form** in Campaign Manager - **Admin** or **Owner** role in SourceLoop ## What this integration does When you enable Lead Gen capture for an ad account, SourceLoop: 1. **Subscribes to LinkedIn's webhook** for that ad account's Lead Gen Forms (one subscription per ad account) 2. **Receives a webhook notification** within seconds of every new Lead Gen submission, containing the form response ID, the campaign and creative URNs, and a timestamp 3. **Fetches the full answer set** from LinkedIn's Lead Form Responses API using your authorised access 4. **Creates a contact in SourceLoop** with the lead's identity (email, phone, name, company, title) and every custom question's answer 5. **Attaches the source attribution** — the ad campaign, ad group, and specific creative URN that drove the submission 6. **Triggers downstream syncs** — CRM push, conversion sync to LinkedIn's own Conversions API for closed-loop reporting, dashboard updates End-to-end latency from submission to contact-in-SourceLoop is typically 10-30 seconds. ## Step 1: Authorise the Lead Sync scope Lead Gen capture requires an extra LinkedIn scope beyond the basic Ads connect. SourceLoop runs a second OAuth flow specifically for it. 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> LinkedIn Ads**. 3. Scroll to the **Lead Gen Form capture** section in the drawer. 4. Click **Enable Lead Gen capture**. SourceLoop redirects you to LinkedIn for a scoped re-authorisation. 5. Sign in to LinkedIn with the user that has Page Admin access on the Pages that own your Lead Forms. 6. Approve the Lead Sync scope and the Page management scopes. 7. LinkedIn redirects back to SourceLoop with the upgraded grant. ## Step 2: Pick which Pages to subscribe to After the OAuth, SourceLoop shows a list of Pages your user has admin access to. 1. Find the Pages that own your Lead Gen Forms (usually your company Page, sometimes additional brand or product Pages). 2. Toggle **Subscribe** on each Page you want to capture leads from. 3. Click **Save subscriptions**. For each Page you subscribe to, SourceLoop calls LinkedIn's API to register a webhook subscription. LinkedIn responds with a subscription confirmation, which is logged on the connection. > **Subscribe at the Page level, not the form level** > LinkedIn's webhook subscriptions are scoped to Pages, not to individual Lead Gen Forms. Once a Page is subscribed, every Lead Gen Form owned by that Page reports submissions to SourceLoop, current forms and any new ones you create later. No per-form setup needed. ## Step 3: Verify the subscription is active 1. In SourceLoop's LinkedIn drawer, the **Lead Gen Form capture** section now shows each subscribed Page with status **Active**, the subscription date, the lifetime count of events received, and the last event timestamp. 2. To test end-to-end, submit a real Lead Gen Form yourself, click into one of your active LinkedIn ads, fill out the form, submit. 3. Within 30 seconds, the new lead should appear in **Contacts Hub** with source = "LinkedIn Lead Ads" plus the campaign and creative attribution. If the lead doesn't appear, see the troubleshooting section in [Troubleshoot LinkedIn Ads sync issues](/help/troubleshoot-linkedin-ads-sync/). ## Step 4: Backfill historical leads (optional) LinkedIn allows fetching the last 90 days of Lead Gen Form responses via the API. On first enable, SourceLoop offers to backfill these into your workspace. 1. After verifying the subscription is active, click **Backfill last 90 days** in the Lead Gen Form capture section. 2. SourceLoop pulls every lead form response from the last 90 days across your subscribed Pages. 3. Backfill typically completes within 5-30 minutes depending on volume. Watch the lead count tick up in Contacts Hub. Leads older than 90 days aren't accessible via the LinkedIn API and can't be backfilled. ## What gets captured per lead For every Lead Gen Form submission, SourceLoop creates a contact with: **Identity:** - Email - First name + last name - Phone (if collected) - Job title (if collected) - Company name (if collected) - Country (if collected) **Attribution:** - **Source channel** = "LinkedIn Lead Ads" - **Campaign URN** + Campaign name (resolved on next Insights sync) - **Creative URN** + Creative name (resolved on next Insights sync) - **Lead Form URN** + Lead Form name - **Submission timestamp** **Custom fields:** - Every custom question on the Lead Form is stored as a custom field on the contact. For example, "Team size, What's your role?, Are you a decision maker?" each become a custom field with the lead's answer. This data is immediately available for filtering, segmenting, dashboard groupings, and CRM sync (HubSpot, Salesforce, Pipedrive all push the standard fields plus the custom ones). ## What gets pushed back to LinkedIn A Lead Gen submission is also a SourceLoop conversion. If you've [configured a Conversions API mapping](/help/configure-linkedin-offline-conversions/) with the **Lead** conversion type, the Lead Gen submission fires that mapping too, so LinkedIn's bidding algorithm sees it as a conversion for the campaign that drove it, closed-loop reporting. You can also have a separate Conversion Rule specifically for Lead Gen Form submissions (recommended) so it gets reported separately in Campaign Manager from other lead types. ## Disabling Lead Gen capture later To stop capturing Lead Gen Forms without disconnecting the rest of LinkedIn Ads: 1. In the **Lead Gen Form capture** section of the LinkedIn drawer, toggle each subscribed Page **off**. 2. Click **Save subscriptions**. SourceLoop calls LinkedIn to unsubscribe each Page. Webhook delivery stops immediately. Existing captured leads stay in your workspace. To completely remove LinkedIn from SourceLoop (including any Lead Gen subscriptions), see [Disconnect LinkedIn Ads from SourceLoop](/help/disconnect-linkedin-ads-from-sourceloop/). ## What's next - **Troubleshoot** Lead Gen captures that aren't showing up: [Troubleshoot LinkedIn Ads sync issues](/help/troubleshoot-linkedin-ads-sync/). - **Configure** the offline conversion push back to LinkedIn so the bidding optimises off your Lead Gen leads: [Configure LinkedIn Conversions API sync](/help/configure-linkedin-offline-conversions/). ## Frequently Asked Questions ### What's a Lead Gen Form on LinkedIn? A LinkedIn Lead Gen Form is a native ad format where the prospect fills out a form without leaving LinkedIn. It's high-conversion because LinkedIn pre-populates the form with the user's profile data (name, email, company, title), no friction. The downside, attribution. The lead never lands on your site, so the SourceLoop tracking pixel never sees them. That's where this article comes in. ### How does SourceLoop capture Lead Gen Form submissions if the lead never visits my site? Via a real-time webhook. After you authorise the Lead Sync subscription in SourceLoop, LinkedIn pushes every new Lead Gen Form response to SourceLoop as it happens. SourceLoop then fetches the form's full answer set from LinkedIn's API and creates a contact in your workspace, with attribution to the originating campaign/creative. ### Does this work for Sponsored Content, Message Ads, and Conversation Ads with embedded Lead Gen Forms? Yes. All three ad formats can embed Lead Gen Forms. As long as the form is configured to use Lead Sync (the default for new forms), SourceLoop captures responses regardless of which ad format triggered the form. ### How fast does a Lead Gen submission appear in SourceLoop? Within seconds of submission. LinkedIn pushes the webhook notification within ~10 seconds of the user clicking Submit, and SourceLoop processes it immediately, fetches the full answer set, and creates the contact. CRM sync (HubSpot, Salesforce, etc.) runs on the next 15-minute cycle. ### What fields get captured from the Lead Gen Form? Every field on the form. The standard ones, first name, last name, email, phone, job title, company, country, are normalised to SourceLoop's contact fields. Custom questions on the form (e.g., "What's your team size?", "Are you a decision-maker?") are stored as custom fields on the contact. The Lead Gen Form name itself is stored too, so you can filter / segment by form in dashboards. ### Do I need a different scope than the basic LinkedIn Ads connect? Yes. Lead Gen Form capture requires an extra Lead Sync scope plus subscribe permission on the specific Pages that own your Lead Forms. SourceLoop runs a second OAuth flow specifically for this when you enable Lead Gen capture. ### I have existing Lead Gen Forms with months of historical leads. Can SourceLoop backfill them? Partially. LinkedIn allows fetching the last 90 days of leadFormResponses via the API, so on first enable, SourceLoop pulls the last 90 days into your workspace. Older leads aren't accessible via the API and can't be backfilled. ### What if the lead's email is missing from the form? LinkedIn auto-populates email from the user's profile by default, but some forms make it optional. If a submission arrives with no email, SourceLoop still captures it (as an anonymous lead with phone, name, and company), but downstream CRM sync skips it because most CRMs require email. You can fix this by making email required in your Lead Gen Form template. ### How is this different from using LinkedIn's CSV export? CSV exports are manual, often a 24-hour delay, no real-time CRM sync, and zero campaign attribution stitching. SourceLoop's webhook capture is real-time, fully automated, and attaches the lead to the specific campaign and creative that drove the submission. Your CRM gets the lead within seconds with full source attribution baked in. --- # How to troubleshoot LinkedIn Ads sync issues Checklist for LinkedIn Ads sync issues. Conversion Rule ID errors, missing CAPI events, Lead Gen webhook delivery, identity matching, OAuth token issues. Source: https://sourceloop.ai/help/troubleshoot-linkedin-ads-sync/ Updated: 2026-05-28 --- LinkedIn Ads sync issues split into three buckets: connection / OAuth, Conversions API push, and Lead Gen Form capture. Work through this checklist in the order it appears. ## Before you start Have these tabs open: - **SourceLoop's LinkedIn Ads card** at **Setup -> Ad Platforms -> LinkedIn Ads** - **LinkedIn Campaign Manager** at Analyze -> Conversion Tracking -> Conversions - For Lead Gen issues: the **Lead Gen Forms** view in Campaign Manager - The most recent **Sync log** entry on the LinkedIn card ## Step 1: Check the connection status 1. Open **Setup -> Ad Platforms -> LinkedIn Ads** in SourceLoop. 2. Look at the card: - **Active** with recent Last sync → healthy - **Active** with stale Last sync (>24 hours) → Insights sync stuck - **Token expired** → reconnect needed - **Disconnected** → run Connect flow ## Step 2: For 'Token expired' / 'Disconnected' 1. Click **Reconnect** on the LinkedIn card. 2. Sign in to LinkedIn with the user that has Campaign Manager access. 3. Authorise the scopes. 4. Pick the same ad account in the picker. If you'd previously enabled Lead Gen Form capture, you may need to re-authorise the Lead Sync scope separately, see [Capture LinkedIn Lead Gen Form submissions](/help/capture-linkedin-lead-gen-forms/). ## Step 3: For 'conversions not appearing in Campaign Manager' This is the most common Conversions API issue. Walk through: 1. **Open the Sync log** on the LinkedIn card (three-dot menu). 2. Find the conversion you expected. Each push is logged with the Conversion Rule URN, status, and any error. - **sent** → SourceLoop pushed the conversion successfully. Campaign Manager has a 4-24 hour reporting delay; allow it. - **pending_identity** → no `li_fat_id`, no email, no phone. The visitor had no matchable signal. Skip these. - **error** → LinkedIn rejected the push. Click the row for the exact error. Common errors: - **`Invalid conversion URN`** → wrong Conversion Rule ID. See FAQ above for how to find the right one. - **`Conversion outside lookback window`** → the conversion timestamp is more than 30 days after the original click. LinkedIn rejects these. - **`Authorization required`** → token expired. Reconnect. - **`Currency not supported`** → conversion value sent in a currency LinkedIn doesn't support for the ad account. Adjust the currency override in your SourceLoop config. ## Step 4: For 'Lead Gen Form submissions not arriving in SourceLoop' This is the most common Lead Gen capture issue. Run through: 1. **Open the LinkedIn drawer in SourceLoop** and scroll to the **Lead Gen Form capture** section. 2. **Confirm the Page that owns your Lead Gen Form is subscribed.** Status should be Active, with a recent **Last event** timestamp if leads have been coming in. 3. **Confirm the Lead Sync scope was authorised** during the Lead Gen enable flow. If the scope wasn't granted, the subscriptions will show **Pending** instead of Active. Re-run the enable flow with a user that has Page Admin. 4. **Confirm the form is configured to use Lead Sync.** In Campaign Manager, open the Lead Gen Form template. The Lead Sync toggle should be on. If the form was created before Lead Sync was added to your account, it may default to off; you'll need to clone the form with Lead Sync enabled. 5. **Test by submitting a real form.** Click into one of your active LinkedIn ads with a Lead Gen Form attached, fill in, submit. Within 30 seconds, watch the subscription's **events received** counter increment and the lead appear in Contacts Hub. ## Step 5: For 'X-LI-Signature verification failed' on webhook deliveries If you see this in the Lead Gen log: 1. **Don't panic, this is the security layer working.** SourceLoop validates every webhook delivery against LinkedIn's HMAC-SHA256 signature using our shared credential. Failed verification means the delivery wasn't actually from LinkedIn (or was tampered with). 2. **Check if this is happening for legitimate LinkedIn events.** If real Lead Gen submissions are failing verification, the shared credential may have rotated on LinkedIn's side. Email **hello@sourceloop.ai** with the specific timestamp and we'll investigate. 3. **If the failures are for unknown sources**, that's the security layer rejecting spoofed requests, no action needed. ## Step 6: For poor Match Score LinkedIn's reporting tells you the Match Score for each Conversion Rule. To improve it: 1. **Install the LinkedIn Insight Tag** site-wide. This is what sets `li_fat_id` on the visitor's first ad-click landing. Without it, identity matching falls back to email or phone only, which produces lower scores. 2. **Capture email in your forms.** SHA256-hashed email is the strongest second identifier after `li_fat_id`. Make email a required field. 3. **Capture phone where appropriate.** Hashed phone adds a third matching signal. 4. **Don't fire conversions for anonymous visitors.** SourceLoop already skips visitors with no identity signal (`pending_identity`). Configure your conversion sources to require email at minimum. Lead Gen Form captures have perfect Match Score by default because email always comes through with the form response. ## For Insights data not updating 1. **Check the last sync timestamp** on the LinkedIn card. Daily at 05:00 UTC. 2. **Force a manual resync.** Click **Resync now**. 3. **Check LinkedIn's own reporting.** If Campaign Manager shows the same lag (common for the last 1-3 days), the data isn't ready on LinkedIn's end. SourceLoop can only show what LinkedIn provides. ## How to disconnect or reset - **Soft reset** (re-run click-mapping backfill): three-dot menu -> **Reset click mapping** on the LinkedIn card. - **Hard reset** (disconnect entirely): see [Disconnect LinkedIn Ads from SourceLoop](/help/disconnect-linkedin-ads-from-sourceloop/). ## When to email support Email **hello@sourceloop.ai** with: - The LinkedIn Ads card's current status - Two or three error messages from the Sync log (verbatim) - Your **Ad Account ID** (visible on the LinkedIn card) - If Lead Gen related, the **Page ID(s)** you subscribed to and the **Lead Form ID** if you can find it We respond within one business day. ## Frequently Asked Questions ### My LinkedIn card shows 'Token expired'. What now? Click Reconnect on the LinkedIn card. LinkedIn access tokens last 60 days; SourceLoop renews them automatically via the stored refresh token. A Token expired state usually means the refresh token itself was revoked (someone removed SourceLoop access at LinkedIn) or both tokens have expired (rare). Reconnect runs a fresh OAuth flow. ### Conversions are firing in SourceLoop but not in Campaign Manager. What should I check? Five common causes. (1) Conversion Rule ID wrong, double-check the numeric ID matches the rule in Campaign Manager. (2) Conversion outside lookback window, LinkedIn rejects events more than 30 days after the click. (3) No identity, the conversion has no li_fat_id, no email, no phone, so LinkedIn rejects it. (4) Reporting delay, Campaign Manager batches conversion display, expect 4-24 hours. (5) Insight Tag not deployed, without the tag, li_fat_id never gets set, dramatically reducing match rate. ### LinkedIn says 'Invalid conversion URN' in the sync log. Why? The Conversion Rule ID in your SourceLoop config doesn't correspond to a valid Conversion Rule in the connected ad account. Open Campaign Manager -> Analyze -> Conversion Tracking -> Conversions, click into the right rule, copy the ID from the URL, update the SourceLoop config. ### My Lead Gen Form submissions aren't reaching SourceLoop. What's wrong? Five causes. (1) The Page that owns the form isn't subscribed in SourceLoop's Lead Gen Form capture section. (2) The Lead Sync OAuth scope wasn't authorised (you'd see this in the drawer's status). (3) The user who authorised doesn't have Page Admin on the Page. (4) The form was created with Lead Sync disabled, check in Campaign Manager. (5) The form is on a Page you don't subscribe to. ### I see 'X-LI-Signature verification failed' in the Lead Gen webhook log. What does that mean? This is a security check, SourceLoop validates that every webhook delivery is signed by LinkedIn using HMAC-SHA256 against our credential. A failed verification usually means an unauthorised attempt to spoof a webhook. SourceLoop rejects the event with HTTP 401. Real LinkedIn deliveries should never fail this check, if you see it happening for real LinkedIn events, email support. ### My Match Score is poor. How do I improve it? Three things. (1) Install the LinkedIn Insight Tag site-wide, this is what sets li_fat_id on first ad-click landing. (2) Capture email in your forms, hashed email is the strongest second signal. (3) Don't push conversions for visitors with no identity, SourceLoop already skips these. Lead Gen Form captures have perfect Match Score by default because email always comes through. ### Insights data is stale. When does it refresh? Insights sync runs daily at 05:00 UTC and re-fetches the last 14 days. Force an immediate refresh by clicking Resync now. Recent campaign performance (last 1-3 days) often lags in LinkedIn itself, you're not behind, LinkedIn is. --- # How to disconnect LinkedIn Ads from SourceLoop Disconnect SourceLoop from your LinkedIn Ads account. What stops, what is deleted immediately, what clears in 30 days, plus Lead Gen webhook removal. Source: https://sourceloop.ai/help/disconnect-linkedin-ads-from-sourceloop/ Updated: 2026-05-28 --- This article covers the LinkedIn Ads disconnect flow end-to-end, what stops, what gets deleted, including the Lead Gen Form webhook unsubscribe step. It complies with LinkedIn's [Marketing Developer Platform terms](https://www.linkedin.com/legal/l/api-terms-of-use) including data deletion requirements for Conversions API and Lead Sync integrations. ## Before you start Set expectations: - **Disconnecting on SourceLoop's side** revokes your LinkedIn authorization, wipes our copy of the access and refresh tokens, unsubscribes any Lead Gen Form webhook subscriptions, and clears every other piece of configuration, all immediately. - **Historical campaign performance data** is deleted asynchronously within 30 days. - **Conversions already received by LinkedIn stay in LinkedIn.** Lead Gen Form responses already captured stay in your SourceLoop workspace. ## Step 1: Disconnect on SourceLoop's side 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> LinkedIn Ads**. 3. On the LinkedIn Ads card, click the three-dot menu (top-right) and select **Disconnect**. 4. Confirm the disconnect dialog. The moment you confirm, SourceLoop runs the full disconnect for you. Here's what happens: 1. **Your LinkedIn authorization is revoked.** SourceLoop tells LinkedIn to invalidate the access we were granted, and we wipe our own copy of both the access and refresh tokens. From this point on, SourceLoop has no way to access your LinkedIn account. 2. **Lead Gen Form webhook subscriptions are cancelled.** For every Page you'd subscribed to, SourceLoop calls LinkedIn's API to unsubscribe. New Lead Gen submissions stop arriving in SourceLoop immediately. 3. **Your conversion sync settings are cleared.** Every Conversion Rule mapping, value source, attribution model, and dedup rule is removed. 4. **Pending conversion pushes are cancelled.** Anything queued to be sent to the Conversions API is dropped. 5. **Your campaign performance history is queued for deletion.** Spend, impressions, clicks, and the click-to-ad lookups used for attribution are removed asynchronously, usually within minutes, always within 30 days. 6. The LinkedIn Ads card on the Ad Platforms tab flips to **Disconnected**. ## Step 2: Revoke SourceLoop on LinkedIn's side (recommended) This step is optional, Step 1 already revokes the OAuth grant. For clean audit trails: 1. Sign in to LinkedIn. 2. Open **Settings & Privacy -> Data Privacy -> Permitted services**. 3. Find **SourceLoop** in the list of authorised applications. 4. Click **Remove**. LinkedIn logs the removal in your account's security activity. Defence in depth. ## Step 3: Decide what to do with your Conversion Rules and Pages SourceLoop doesn't create or modify Conversion Rules, Pages, or Lead Gen Forms on LinkedIn's side. So there's nothing on LinkedIn's end for SourceLoop to clean up. But you can: - **Keep the Conversion Rules** as-is. They continue to receive any other CAPI integrations you have. If you want to wind them down, archive each in Campaign Manager. - **Leave your Pages and Lead Gen Forms alone.** SourceLoop's webhook subscriptions are gone; the Pages and forms themselves are untouched. - If you'd like the historical captured Lead Gen leads removed from SourceLoop too, see "GDPR / full data removal" below. ## What SourceLoop holds, and what gets deleted ### Deleted immediately at disconnect These go away the moment you click Disconnect: - **Your LinkedIn authorization** (revoked on LinkedIn's side and removed from ours) - **The connection itself**, including the Ad Account ID, account name, and currency - **All your conversion sync settings**, Conversion Rule mappings, value sources, attribution choices, dedup windows - **All Lead Gen Form webhook subscriptions** on the Pages you'd authorised (unsubscribed via LinkedIn's API) - **All pending conversion pushes** that hadn't yet been sent to the Conversions API - **Your sync run history** for this connection ### Deleted within 30 days These are queued for asynchronous deletion. Most clear within minutes; our privacy commitment guarantees completion within 30 days: - **Your campaign performance history in SourceLoop** (spend, impressions, clicks per campaign / campaign group / creative, collected during your connection) - **The click-to-ad lookups** used for attribution ### Retained (unless you explicitly request deletion) - **Lead Gen Form responses already captured** as contact records in your SourceLoop workspace. These are now part of your customer data, not part of the LinkedIn connection itself. They stay until you remove them or request GDPR deletion (see below). ### Never held in the first place For transparency, SourceLoop also does not, at any point, access: - Your LinkedIn personal messages, connections, or any non-advertising data - Profile content beyond your name and email (used solely to label the connection in our UI) - Audience segments inside your ad account beyond what's needed to push conversion events ## Confirming the deletion For compliance audits: 1. Email **hello@sourceloop.ai** with the subject "Confirm LinkedIn Ads deletion" and your LinkedIn Ad Account ID. 2. We reply within 2 business days with deletion timestamps for each data category. This is suitable for SOC2, ISO27001, and GDPR Article 17 audit trails. ## GDPR / removing captured Lead Gen leads If you want SourceLoop to also remove the historical Lead Gen Form responses captured during your connection, email **hello@sourceloop.ai** with the subject "GDPR Lead Gen deletion request" and your Ad Account ID. We remove the lead contact records and all derived analytics within 30 days, with written confirmation. This is irreversible. After completion, the captured leads cannot be restored. ## Reconnecting later You can reconnect at any time. Disconnect wipes the connection and its settings, so reconnect starts fresh: 1. Sign in to SourceLoop. 2. Open **Setup -> Ad Platforms -> LinkedIn Ads**. 3. Click **Connect**. 4. Run the OAuth flow. 5. Pick the same ad account. 6. **Re-add your Conversion Rule mappings** in the Conversion sync section. 7. **Re-enable Lead Gen Form capture** if you want it (separate OAuth flow for the Lead Sync scope). Your previous campaign performance history is gone, that's what the 30-day deletion covered. Reconnect pulls in fresh data going forward. ## Privacy and security For full transparency: - **What we ask for:** read and write access to the LinkedIn ad accounts you authorise. For Lead Gen capture, an additional Lead Sync scope plus subscribe permission on the specific Pages you authorise. Nothing else, no personal messaging, no connections, no public profile data beyond your name and email. - **How we store your authorization:** encrypted at rest with industry-standard practices. The raw access token and refresh token never appear in logs or in our UI. - **Automatic renewal:** SourceLoop renews access in the background as needed via the stored refresh token. The renewal stops the moment you disconnect. - **Third parties:** SourceLoop does not sell, transfer, or share LinkedIn-sourced data with anyone outside SourceLoop. - **LinkedIn Platform Terms:** SourceLoop complies with LinkedIn's [Marketing Developer Platform terms](https://www.linkedin.com/legal/l/api-terms-of-use), including the Conversions API and Lead Sync requirements. - **Encryption in transit:** all communication with LinkedIn's APIs uses TLS 1.2+. - **Webhook integrity:** every Lead Gen Form webhook delivery is verified via HMAC-SHA256 against our shared credential, requests with invalid signatures are rejected and logged. ## When to email support For anything outside the standard flow: - **"I disconnected by mistake"** → email hello@sourceloop.ai; we can prioritise restoring the configuration if you act within 24 hours - **"Our compliance team needs a written DPA"** → we provide a signed DPA on request - **"An auditor needs evidence of deletion completion"** → we provide written confirmation keyed to your Ad Account ID - **"LinkedIn still shows SourceLoop as authorised"** → complete Step 2 above Email **hello@sourceloop.ai** for any of the above. We respond within one business day. ## Frequently Asked Questions ### How quickly does SourceLoop stop pushing conversions after I disconnect? Immediately. The moment you confirm Disconnect, SourceLoop revokes your LinkedIn authorization, wipes our copy of the access and refresh tokens, and clears the connection. The 2-minute Conversions API push cycle queries active connections only, so the very next cycle excludes yours. ### Does disconnecting also stop Lead Gen Form webhook deliveries? Yes. As part of the disconnect, SourceLoop calls LinkedIn to unsubscribe from any Lead Gen Form webhook subscriptions on Pages you'd authorised. LinkedIn stops sending notifications immediately. Existing captured leads stay in your SourceLoop workspace. ### Does disconnecting delete conversions I've already pushed to LinkedIn? No. Conversions already received by the LinkedIn Conversions API stay in LinkedIn. Campaign Manager reports continue to show them; bidding signals trained on those events stay in effect. ### What gets deleted immediately vs within 30 days? Deleted immediately, your LinkedIn authorization, all conversion sync settings, the Conversion Rule mappings, sync history, pending pushes, and any Lead Gen Form webhook subscriptions on subscribed Pages. Deleted within 30 days, your campaign performance history in SourceLoop (spend, impressions, clicks per campaign / campaign group / creative) and the click-to-ad lookups used for attribution. Lead Gen Form responses already captured stay in your workspace as part of your contact data. ### How do I verify the deletion? Email hello@sourceloop.ai with the subject "Confirm LinkedIn Ads deletion" and your LinkedIn Ad Account ID. We provide a written confirmation with deletion timestamps for each data category, suitable for compliance audits. ### Will reconnecting later restore my settings? No. Disconnect wipes the connection and its configuration. Reconnecting starts fresh, you'll re-enter the Conversion Rule mappings and re-enable Lead Gen Form capture if you want it. ### I enabled Lead Gen capture for several Pages. Do I need to unsubscribe them manually? No. SourceLoop unsubscribes every Page automatically as part of the disconnect flow. The Pages themselves stay yours, untouched. ### Will disconnecting LinkedIn affect my other ad platforms? No. Each ad platform connection is independent. --- # Microsoft Ads Conversion Tracking & Attribution Setup Guide Connect Microsoft Ads (formerly Bing Ads) to push offline conversions via msclkid. Multi-account support, token refresh, Smart Bidding optimisation. Source: https://sourceloop.ai/help/connect-microsoft-ads/ Updated: 2026-05-28 --- Connecting Microsoft Ads to SourceLoop closes the offline conversion loop for Bing, Yahoo, and the Microsoft Audience Network. Every time a tracked lead converts on your site (form submission, meeting booked, payment received), SourceLoop pushes that conversion back to Microsoft Ads against the matching msclkid. The bidding strategies train on real revenue, not just whatever the browser pixel saw. Microsoft Ads is often the under-loved cousin of Google Ads, but it converts well for B2B and tends to have meaningfully lower CPCs. Offline conversion sync magnifies the ROAS difference. This article covers the OAuth connect flow only. After connecting, see: - [Configure Microsoft Ads offline conversion sync](/help/configure-microsoft-ads-offline-conversions/) - [Troubleshoot Microsoft Ads sync issues](/help/troubleshoot-microsoft-ads-sync/) ## Why connect Microsoft Ads to SourceLoop? Microsoft Ads' Universal Event Tracking (UET) tag fires browser-side, which means it captures maybe 50-60% of conversions cleanly. The Offline Conversions API fills in the rest: - **Server-side conversion push** via the Microsoft Ads Offline Conversions API, immune to browser-side blocking - **msclkid matching** for every conversion where the visitor clicked through from a Microsoft ad - **Revenue values** sent with every conversion when payment integrations are connected - **Smart Bidding optimisation** trains on real qualified leads / paying customers, not just clicks - **Multi-touch attribution** within SourceLoop, while feeding Microsoft the offline conversion data it needs for tCPA-style bidding ## Before you start You'll need: - A **SourceLoop workspace** with the [tracking pixel installed](/help/install-the-tracking-pixel/) - A **Microsoft Ads account** with Auto-tagging enabled (so msclkid is added to ad-click landing URLs) - A Microsoft user with **Standard access** or higher on the ad account - An existing **Conversion Goal** in Microsoft Ads (you'll get the Goal Name for SourceLoop's config in the next article) - **Admin** or **Owner** role in SourceLoop ## Step 1: Open the Ad Platforms page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Setup** in the left sidebar. 3. Click the **Ad Platforms** tab. 4. Scroll to the **Microsoft Ads** card and click **Connect**. You'll be redirected to Microsoft's OAuth consent screen on `login.microsoftonline.com`. ## Step 2: Authorise SourceLoop on Microsoft 1. Sign in with the Microsoft user that has Standard+ access on your Microsoft Ads account. 2. Pick the right work or personal account if you're prompted (Microsoft accounts can have multiple identities). 3. Review the requested scopes (msads.manage and offline_access). 4. Click **Yes / Accept**. Microsoft redirects you back to SourceLoop with an authorisation code, which SourceLoop exchanges for an access token + refresh token. Tokens are stored encrypted. ## Step 3: Pick the Microsoft Ads account If your Microsoft user has access to more than one Microsoft Ads account (common for agencies and Manager accounts), SourceLoop shows a picker. 1. Find the Microsoft Ads account you want to connect. 2. Click **Connect this account**. SourceLoop creates a connection for that specific account. The Microsoft Ads card flips to **Connected** with the account name and Account ID. > **To connect another account, repeat the flow** > Each Microsoft Ads account is its own connection. To connect multiple accounts (e.g., one per brand or region), run the Connect Microsoft Ads flow once per account. ## Step 4: Confirm Auto-tagging is enabled Microsoft Ads' Auto-tagging adds the msclkid parameter to every ad-click landing URL. Without it, your SourceLoop tracker has nothing to capture, and offline conversions can't be matched back to clicks. 1. Open [Microsoft Ads](https://ads.microsoft.com/). 2. Go to **Settings -> Account settings -> Account preferences**. 3. Confirm **Auto-tagging of UTM** is enabled (or at minimum **Auto-tagging of msclkid**). 4. Save if you had to enable it. Once on, every new ad click adds `?msclkid=...` to the landing URL. SourceLoop's tracker captures that the first time the visitor lands. Existing visitors without msclkid in their session can't be retroactively tagged, you'll start seeing conversions push 1-2 weeks after enabling, as Auto-tagged sessions accumulate. ## What gets synced Once connected and configured, SourceLoop runs two flows automatically: **PULL (Insights sync, daily at 05:00 UTC):** - Campaign, ad group, and ad hierarchy - Daily spend, impressions, clicks per campaign / ad group / ad - 14-day rolling re-sync to catch Microsoft's late attribution **PUSH (Offline Conversions API, every 2 minutes):** - Every SourceLoop conversion where the visitor's session carries an `msclkid` cookie - Conversion value (currency) and currency code when configured - Conversion Name and Conversion Time for matching to your Microsoft Ads Conversion Goal ## What's next - **Pick which SourceLoop events get pushed to which Microsoft Ads Conversion Goal:** [Configure Microsoft Ads offline conversion sync](/help/configure-microsoft-ads-offline-conversions/). - **Troubleshoot** any push errors: [Troubleshoot Microsoft Ads sync issues](/help/troubleshoot-microsoft-ads-sync/). - **Disconnect or reset** the integration: [Disconnect Microsoft Ads from SourceLoop](/help/disconnect-microsoft-ads-from-sourceloop/). ## Frequently Asked Questions ### Is this Microsoft Ads or Bing Ads? Same thing. Microsoft renamed Bing Ads to Microsoft Advertising in 2019, but plenty of marketers still call it Bing Ads. The underlying account and APIs are the same. Microsoft Ads also covers ads on Yahoo, AOL, MSN, Outlook.com, and the Microsoft Audience Network. ### Do I need a Microsoft Ads Developer Token? No. SourceLoop holds the Developer Token on our side, so you don't need to apply for one yourself. You just authorise the OAuth flow and we handle the rest. ### What does Microsoft use to match offline conversions to clicks? msclkid (Microsoft Click ID), and only msclkid. Unlike Google Ads or Meta, Microsoft Ads doesn't support PII fallback for offline conversions, no hashed email, no hashed phone. If a visitor's session doesn't have an msclkid cookie, the conversion can't be pushed. ### What scopes does SourceLoop request from Microsoft? The required scopes are msads.manage (read/write to the Microsoft Ads API) and offline_access (so SourceLoop can refresh tokens automatically). We don't ask for any non-advertising data. ### How long does the Microsoft Ads access token last? 1 hour. SourceLoop stores the refresh token and rotates the access token automatically as needed, you won't need to reconnect unless the refresh token itself gets revoked. ### Can I connect a Microsoft Ads Manager (agency) account? Yes, but you connect each sub-account individually. The Manager account itself can't run offline conversions, only the individual ad accounts under it can. After OAuth, SourceLoop shows the list of accounts accessible to your user and you pick one per connection. ### Does this work for the Microsoft Audience Network? Yes. Microsoft Audience Network conversions report through the same offline conversion endpoint, scoped to your Microsoft Ads account. If your campaigns target the Audience Network, conversions push the same way as Search. --- # How to Sync Revenue and Offline Conversions in Microsoft Ads Set up Microsoft Ads offline conversion push. Create a Conversion Goal, find the Goal Name, and map SourceLoop events to push via msclkid. Source: https://sourceloop.ai/help/configure-microsoft-ads-offline-conversions/ Updated: 2026-05-28 --- After you've [connected Microsoft Ads to SourceLoop](/help/connect-microsoft-ads/), the next step is wiring up the offline conversion push. You'll create a Conversion Goal in Microsoft Ads, find its name, and map SourceLoop events to it. ## Before you start You'll need: - [Microsoft Ads connected to SourceLoop](/help/connect-microsoft-ads/) (the card shows the account name) - Access to **Microsoft Ads** for the account you connected (Standard or Admin role) - A clear idea of which SourceLoop events you want to track in Microsoft Ads (typically: Lead, Demo Booked, Paid Signup) - **Admin** or **Owner** role in SourceLoop ## Step 1: Create the Conversion Goal in Microsoft Ads If you already have a Conversion Goal set up for offline conversions, skip to Step 2. 1. Sign in to [Microsoft Ads](https://ads.microsoft.com/). 2. Pick the account you connected to SourceLoop. 3. Go to **Tools -> Conversion Goals**. 4. Click **Create Conversion Goal**. 5. Pick the goal type that matches your funnel stage: - **Other** — generic, works for most lead-style conversions - **Purchase** — one-off ecommerce sales - **Sign-up** — account registrations - **Lead** — form submissions 6. Pick **Offline** as the **Conversion source**. 7. Name it something specific (e.g., "SourceLoop, Demo Booked"). **The exact name is what you'll paste into SourceLoop, so make it memorable and consistent.** 8. Set the **Count** type (typically Unique for B2B leads, All for ecommerce). 9. Set the **Conversion Window** (default 30 days, the maximum Microsoft allows). 10. Set the **Default Conversion Value** if you want a baseline value (you'll override per-conversion in SourceLoop). 11. Save the goal. Repeat for each separate funnel stage you want to track. ## Step 2: Find the exact Goal Name The critical detail: Microsoft Ads matches offline conversions by **name**, not by a numeric ID. So the name in SourceLoop must match the Goal name in Microsoft Ads exactly, including capitalisation, spaces, and punctuation. 1. In Microsoft Ads, go to **Tools -> Conversion Goals**. 2. Find the goal you just created (or the existing one you want to use). 3. **Copy the Goal Name exactly** as shown in the table. Be careful with special characters; even a trailing space can break the match. ## Step 3: Add the conversion mapping in SourceLoop 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> Microsoft Ads**. 3. Scroll to **Conversion sync**. 4. Click **Add conversion mapping**. 5. Pick the **Trigger event type**: - **Conversion created** — every new lead conversion (form, meeting, chat with email, initial payment) - **Expected revenue updated** — when expected revenue changes - **Realized revenue updated** — when a payment integration confirms revenue 6. (Optional) Set a **Trigger event name filter** to scope the mapping. 7. Pick an **Attribution model**: **Last touch** or **First touch**. 8. Paste the **Conversion Goal Name** from Step 2 (exactly as it appears in Microsoft Ads). 9. Toggle **Include value** if you want to send a revenue figure. Pick the value source and currency. 10. Set the **dedup window** in minutes (default 1440). 11. Click **Save**. SourceLoop starts pushing matching conversions on the next 2-minute flush cycle. Microsoft Ads will show the conversion counts within 4-12 hours. > **The Goal Name has to match exactly** > Microsoft uses the Goal Name as the match key, not an ID. Capitalisation, spaces, hyphens, all matter. If your config name is "SourceLoop, Demo Booked" but the Microsoft Goal is "SourceLoop, Demo booked" (lowercase b), the push fails silently. Copy-paste rather than retyping. ## What gets sent in each event For every matching SourceLoop conversion, the push to Microsoft Ads includes: - **ConversionName** — the Goal Name from your mapping - **ConversionTime** — when the conversion happened (ISO 8601) - **MicrosoftClickId (msclkid)** — captured from the visitor's session - **ConversionValue** — when value is included - **ConversionCurrencyCode** — when value is included (defaults to USD) Note that Microsoft Ads requires msclkid for offline conversions, there's no PII fallback. Conversions for visitors with no msclkid are skipped (marked `pending_identity` in the Sync log). ## Why isn't every SourceLoop conversion being pushed? You'll typically see push rates around 40-70% in the first weeks after Auto-tagging is enabled, climbing to 80-95% as msclkid-tagged sessions accumulate. The skipped conversions are visitors who: - Came in via SEO / direct (no msclkid) - Clicked in before Auto-tagging was enabled - Had cookies cleared between click and conversion (rare but happens) These are still tracked in SourceLoop, they just can't be pushed back to Microsoft Ads because there's no msclkid to attribute them to. ## What's next - **Troubleshoot pushes that aren't appearing in Microsoft Ads:** [Troubleshoot Microsoft Ads sync issues](/help/troubleshoot-microsoft-ads-sync/). - **Add more conversion mappings** for separate funnels: repeat Step 3 with each Conversion Goal Name. ## Frequently Asked Questions ### Where do I find the Conversion Goal Name? Microsoft Ads matches offline conversions by Goal Name (not Goal ID, unlike Google Ads). Open Microsoft Ads -> Tools -> Conversion Goals. The Goal Name is the human-readable name you gave the goal when you created it (e.g., "Demo Booked", "Lead Form Submitted"). Copy it exactly, case-sensitive. ### What if I haven't created a Conversion Goal yet? Create one first. In Microsoft Ads, go to Tools -> Conversion Goals -> Create Conversion Goal. Pick Offline as the source. Name it something meaningful like "SourceLoop, Demo Booked". Set the Conversion Window (default 30 days, the maximum). Save. Then come back to SourceLoop and use that exact Goal Name. ### What's msclkid and why does Microsoft Ads need it? msclkid is Microsoft Ads' Click ID, like Google's gclid or Meta's fbclid. It's automatically added to ad-click landing URLs when Auto-tagging is on (Microsoft Ads -> Settings -> Account preferences). SourceLoop's tracker captures the msclkid the first time the visitor lands. Microsoft uses it to match every offline conversion back to the original click. Unlike other platforms, Microsoft Ads doesn't support PII fallback for offline conversions, msclkid is the only signal. ### My SourceLoop conversions show pending_identity. What does that mean? The visitor's session has no msclkid, meaning they didn't click in from a Microsoft ad (or they clicked in before Auto-tagging was enabled). Microsoft can't match the conversion to a click, so SourceLoop skips it. Make sure Auto-tagging is enabled in Microsoft Ads and wait 1-2 weeks for new msclkid-tagged sessions to accumulate. ### Can I send revenue values? Yes. In SourceLoop's conversion mapping, toggle Include value and pick the value source (quote_value, sales_value, or fixed). Microsoft Ads uses the value for tROAS-style bidding. Currency defaults to USD if not set. ### How long does it take for conversions to appear in Microsoft Ads? Typically 4-12 hours after the push. Microsoft Ads batches conversion reporting overnight, so the first conversions you push today may show up tomorrow. SourceLoop's Sync log shows the push completing within 2 minutes; Microsoft just takes longer to surface it. ### Can the same SourceLoop event push to multiple Microsoft Ads Conversion Goals? Yes. Add multiple configuration rows in SourceLoop with the same trigger but different Conversion Goal Names. Each fires independently. --- # How to troubleshoot Microsoft Ads sync issues Checklist for Microsoft Ads sync issues. Goal Name mismatches, msclkid missing, OAuth token errors, Auto-tagging configuration, and how to verify pushes. Source: https://sourceloop.ai/help/troubleshoot-microsoft-ads-sync/ Updated: 2026-05-28 --- Microsoft Ads sync issues fall into a handful of buckets. Work through this checklist in order. ## Before you start Have these tabs open: - **SourceLoop's Microsoft Ads card** at **Setup -> Ad Platforms -> Microsoft Ads** - **Microsoft Ads** at Tools -> Conversion Goals (for verifying Goal Names) - **Microsoft Ads** at Settings -> Account preferences (for Auto-tagging) - The most recent **Sync log** entry on the Microsoft Ads card ## Step 1: Check the connection status 1. Open **Setup -> Ad Platforms -> Microsoft Ads** in SourceLoop. 2. Look at the card: - **Active** with recent Last sync → healthy - **Active** with stale Last sync (>24 hours) → Insights sync stuck - **Token expired** → reconnect needed - **Disconnected** → run Connect flow ## Step 2: For 'Token expired' / 'Disconnected' 1. Click **Reconnect** on the Microsoft Ads card. 2. Sign in to Microsoft with a user that has Standard+ access on the Microsoft Ads account. 3. Authorise the scopes. 4. Pick the same Microsoft Ads account in the picker. ## Step 3: For 'conversions not appearing in Microsoft Ads' This is the most common issue. Walk through: 1. **Open the Sync log** on the Microsoft Ads card (three-dot menu). 2. Find the conversion you expected. Each push is logged with the Goal Name, msclkid, value, and status: - **sent** → SourceLoop pushed successfully. Microsoft has a 4-12 hour reporting delay. - **pending_identity** → no msclkid in the visitor's session. Microsoft can't match. See Step 5. - **error** → Microsoft rejected the push. Click the row for the exact error. Common errors: - **`Conversion Goal not found`** → Goal Name mismatch. Copy the exact name from Microsoft Ads. - **`Authorization failed`** → token expired. Click Reconnect. - **`Click ID not found / invalid`** → the msclkid in your push doesn't match a real click in Microsoft's records. Usually means the click was outside the 90-day window or the msclkid was modified. - **`Conversion outside lookback window`** → the conversion is more than 30 days after the click. ## Step 4: For Goal Name mismatch Microsoft matches offline conversions by name, exact match required. To debug: 1. Open Microsoft Ads -> Tools -> Conversion Goals. 2. Copy the Goal Name from the Microsoft table (avoid retyping, use copy-paste). 3. In SourceLoop, open the Microsoft Ads drawer -> Conversion sync. 4. Edit the mapping with the wrong Goal Name and paste the correct name. 5. Save. The next 2-minute flush retries any conversions that failed for this reason. ## Step 5: For 'most conversions show pending_identity' This means msclkid isn't reaching SourceLoop. Three sub-causes: 1. **Auto-tagging disabled in Microsoft Ads.** Go to Microsoft Ads -> Settings -> Account preferences. Confirm Auto-tagging is on. If you just enabled it, you'll need to wait 1-2 weeks for new msclkid-tagged sessions to accumulate before conversion push rate improves. 2. **SourceLoop tracking pixel not on Microsoft Ads landing pages.** Make sure the SourceLoop snippet is installed on every page that receives Microsoft Ads traffic. The tracker captures msclkid the first time the visitor lands. 3. **Visitors converting without ever clicking a Microsoft ad.** Most of your traffic might not be from Microsoft (it's coming from SEO, direct, email, etc.). Those conversions can't be pushed to Microsoft because there's no Microsoft click to attribute them to. This is by design. ## Step 6: For Insights data not updating 1. **Check the last sync timestamp** on the Microsoft Ads card. Daily at 05:00 UTC. 2. **Force a manual resync.** Click **Resync now**. 3. **Check Microsoft's own reporting.** Recent campaign data lags in Microsoft Ads itself for the first 1-3 days. ## How to disconnect or reset - **Hard reset** (disconnect entirely): see [Disconnect Microsoft Ads from SourceLoop](/help/disconnect-microsoft-ads-from-sourceloop/). ## When to email support Email **hello@sourceloop.ai** with: - The Microsoft Ads card's current status - Two or three error messages from the Sync log (verbatim) - Your **Microsoft Ads Account ID** (visible on the Microsoft Ads card) - The **Conversion Goal Name(s)** you configured We respond within one business day. ## Frequently Asked Questions ### My Microsoft Ads card shows 'Token expired'. What now? Click Reconnect on the Microsoft Ads card. Microsoft access tokens are 1-hour-lived; SourceLoop refreshes them via the stored refresh token automatically. A Token expired state usually means the refresh token itself was revoked or expired. Reconnect runs a fresh OAuth flow. ### Conversions are firing in SourceLoop but not in Microsoft Ads. What should I check? Four common causes. (1) Goal Name mismatch, Microsoft matches by name, exact match required. Even a trailing space or capitalisation difference breaks it. (2) msclkid missing, the visitor's session has no msclkid, so Microsoft can't match. (3) Auto-tagging isn't enabled in Microsoft Ads, no msclkid is being added to landing URLs. (4) Conversion outside lookback window, Microsoft rejects events more than 30 days after the click. ### Microsoft says 'Conversion Goal not found' in the sync log. Why? The Goal Name in your SourceLoop config doesn't match any Conversion Goal in the connected account. Open Microsoft Ads -> Tools -> Conversion Goals, copy the exact name (case-sensitive, including punctuation), and update the SourceLoop config. ### Most of my conversions show 'pending_identity'. What's wrong? The `pending_identity` status means the visitor had no msclkid, so Microsoft can't match the conversion. Check three things. (1) Auto-tagging enabled in Microsoft Ads (Settings -> Account preferences -> Auto-tagging). (2) The SourceLoop tracking pixel is installed on every landing page, especially Microsoft Ads landing pages. (3) Visitors actually click in from Microsoft Ads, not all of your traffic is from Microsoft. SEO/Direct/email visitors won't have msclkid by design. ### I see 'Developer Token not authorised' in the sync log. What does that mean? This is rare. It means SourceLoop's Developer Token isn't authorised for your specific Microsoft Ads account, which usually happens if your account is in a region or tier where Microsoft restricts API access. Email hello@sourceloop.ai with your Account ID and we'll investigate. ### How do I send a test offline conversion? Microsoft Ads doesn't have a Test Events tab like Meta or TikTok. Instead, fire a real conversion on your site, in an incognito window with `?msclkid=test_msclkid_123` appended to the URL. SourceLoop will push the conversion with that fake msclkid. Microsoft will reject it (since the msclkid doesn't match a real click), but you can see the push attempt in SourceLoop's Sync log, confirming the integration is wired correctly. ### Insights data is stale. When does it refresh? Insights sync runs daily at 05:00 UTC and re-fetches the last 14 days. Force an immediate refresh by clicking Resync now. Recent campaign performance (last 1-3 days) often lags in Microsoft itself. --- # How to disconnect Microsoft Ads from SourceLoop Disconnect SourceLoop from your Microsoft Ads (Bing) account. What stops, what is deleted immediately, what clears in 30 days, and how to confirm removal. Source: https://sourceloop.ai/help/disconnect-microsoft-ads-from-sourceloop/ Updated: 2026-05-28 --- This article covers the Microsoft Ads disconnect flow end-to-end, what stops, what gets deleted, and how to confirm full removal. It complies with the [Microsoft Advertising API terms](https://learn.microsoft.com/en-us/advertising/guides/get-started) including data deletion requirements for Offline Conversions integrations. ## Before you start Set expectations: - **Disconnecting on SourceLoop's side** revokes your Microsoft authorization, wipes the access and refresh tokens, and clears every other piece of configuration for this connection, all immediately. - **Historical campaign performance data** is deleted asynchronously within 30 days. - **Conversions already received by Microsoft Ads stay in Microsoft Ads.** ## Step 1: Disconnect on SourceLoop's side 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Ad Platforms -> Microsoft Ads**. 3. On the Microsoft Ads card, click the three-dot menu (top-right) and select **Disconnect**. 4. Confirm the disconnect dialog. The moment you confirm, SourceLoop runs the full disconnect for you: 1. **Your Microsoft authorization is revoked.** SourceLoop tells Microsoft to invalidate the access we were granted, and we wipe our own copy of both the access and refresh tokens. From this point on, SourceLoop has no way to access your Microsoft Ads account. 2. **Your conversion sync settings are cleared.** Every Conversion Goal Name mapping, value source, attribution model, and dedup rule is removed. 3. **Pending conversion pushes are cancelled.** Anything queued to be sent is dropped. 4. **Your campaign performance history is queued for deletion.** Spend, impressions, clicks, and the click-to-ad lookups used for attribution are removed asynchronously, usually within minutes, always within 30 days. 5. The Microsoft Ads card on the Ad Platforms tab flips to **Disconnected**. ## Step 2: Remove SourceLoop on Microsoft's side (recommended) This step is optional, Step 1 already revokes the OAuth grant. For clean audit trails: 1. Sign in to [Microsoft My Account](https://myaccount.microsoft.com/) (the same account you used to authorise SourceLoop). 2. Open **Privacy -> Apps and services**. 3. Find **SourceLoop** in the list of authorised apps. 4. Click **Remove**. Microsoft logs the removal in your account's activity log. ## Step 3: Decide what to do with your Microsoft Ads Conversion Goals SourceLoop doesn't create Conversion Goals in Microsoft Ads (you create them manually). So there's nothing for SourceLoop to clean up on Microsoft's side. After disconnect: - **Your Conversion Goals stay** in Microsoft Ads with their accumulated conversion counts and values. - **Bidding strategies using those Goals** continue, just without new offline conversions arriving. - You can **archive or delete the Goals** manually in Microsoft Ads -> Tools -> Conversion Goals if you no longer want them. ## What SourceLoop holds, and what gets deleted ### Deleted immediately at disconnect These go away the moment you click Disconnect: - **Your Microsoft authorization** (revoked on Microsoft's side and removed from ours) - **The connection itself**, including the Account ID, account name, and currency - **All your conversion sync settings**, Conversion Goal Name mappings, value sources, attribution model choices, dedup windows - **All pending conversion pushes** that hadn't yet been sent - **Your sync run history** for this connection ### Deleted within 30 days These are queued for asynchronous deletion. Most clear within minutes; our privacy commitment guarantees completion within 30 days: - **Your campaign performance history in SourceLoop** (spend, impressions, clicks per campaign / ad group / ad, collected during your connection) - **The click-to-ad lookups** used for attribution ### Never held in the first place For transparency, SourceLoop also does not, at any point, access: - Your Microsoft personal account data (email, calendar, contacts, OneDrive, etc.) - Anything outside the Microsoft Ads account you authorised ## Confirming the deletion For compliance audits: 1. Email **hello@sourceloop.ai** with the subject "Confirm Microsoft Ads deletion" and your Microsoft Ads Account ID. 2. We reply within 2 business days with deletion timestamps for each data category. Suitable for SOC2, ISO27001, and GDPR Article 17 audit trails. ## Reconnecting later You can reconnect at any time. Disconnect wipes the connection and its settings, so reconnect starts fresh: 1. Sign in to SourceLoop. 2. Open **Setup -> Ad Platforms -> Microsoft Ads**. 3. Click **Connect**. 4. Run the OAuth flow. 5. Pick the same Microsoft Ads Account. 6. **Re-add your Conversion Goal Name mappings.** ## Privacy and security For full transparency: - **What we ask for:** read and write access to the Microsoft Ads accounts you authorise (`msads.manage` scope), plus `offline_access` so SourceLoop can refresh tokens automatically. Nothing else. - **How we store your authorization:** encrypted at rest with industry-standard practices. The raw access and refresh tokens never appear in logs or in our UI. - **Automatic renewal:** SourceLoop renews access in the background via the stored refresh token. The renewal stops the moment you disconnect. - **Third parties:** SourceLoop does not sell, transfer, or share Microsoft Ads-sourced data with anyone outside SourceLoop. - **Microsoft Advertising API Terms:** SourceLoop complies with the [Microsoft Advertising API Terms](https://learn.microsoft.com/en-us/advertising/guides/get-started) including the offline conversions requirements. - **Encryption in transit:** all communication with Microsoft's APIs uses TLS 1.2+. ## When to email support For anything outside the standard flow: - **"I disconnected by mistake"** → email hello@sourceloop.ai - **"Our compliance team needs a written DPA"** → we provide a signed DPA on request - **"An auditor needs evidence of deletion"** → we provide written confirmation keyed to your Account ID Email **hello@sourceloop.ai** for any of the above. We respond within one business day. ## Frequently Asked Questions ### How quickly does SourceLoop stop pushing conversions after I disconnect? Immediately. The moment you confirm Disconnect, SourceLoop revokes your Microsoft authorization, wipes our copy of the access and refresh tokens, and clears the connection. The 2-minute conversion-push cycle queries active connections only, so the very next cycle excludes yours. ### Does disconnecting delete conversions I've already pushed to Microsoft Ads? No. Conversions already received by the Microsoft Ads Offline Conversions API stay in Microsoft Ads. Campaign reports continue to show them; bidding strategies trained on those events stay in effect. ### What gets deleted immediately vs within 30 days? Deleted immediately, your Microsoft authorization, all conversion sync settings, the Conversion Goal Name mappings, sync history, and any pending pushes. Deleted within 30 days, your campaign performance history in SourceLoop (spend, impressions, clicks per campaign / ad group / ad) and the click-to-ad lookups used for attribution. ### How do I verify the deletion? Email hello@sourceloop.ai with the subject "Confirm Microsoft Ads deletion" and your Microsoft Ads Account ID. We provide a written confirmation with deletion timestamps for each data category, suitable for compliance audits. ### Will reconnecting later restore my Conversion Goal mappings? No. Disconnect wipes the connection and its configuration. Reconnecting starts fresh, you'll re-add Conversion Goal Name mappings, attribution model, value source, and dedup window. ### Will disconnecting Microsoft Ads affect my other ad platforms? No. Each ad platform connection is independent. --- # Section: Reporting & analytics Premade dashboards, attribution models, funnels, and the Contacts Hub. Slice traffic, content, devices, and ad performance. # How to Read the SourceLoop Traffic Dashboard The Traffic dashboard, the marketing-attribution view tying every visitor session, conversion, and dollar of revenue back to the channel that produced it. Source: https://sourceloop.ai/help/traffic-dashboard/ Updated: 2026-05-28 --- The **Traffic dashboard** is the home page of SourceLoop's marketing attribution. It ties every visitor session, conversion, and dollar of revenue back to the channel, source, and campaign that produced it, with seven attribution models you can switch between in a single click. This guide walks through what each part of the dashboard shows, how to filter it, and how to read the breakdown tables underneath. ## What the Traffic dashboard answers In one screen, the Traffic dashboard answers: - **Which marketing channels brought traffic** this period (Paid Search, Organic, Social, Direct, Email, Referral, Paid Social) - **How many of those visits converted** (form, meeting, chat, payment) - **How much revenue each channel produced** (when payment integrations are connected) - **How those numbers shift** when you change attribution model (first-touch, last-touch, linear, etc.) The dashboard is the right place to start any "is our marketing working?" investigation. Funnels and the Contacts Hub go deeper into specific journeys; Traffic is the bird's-eye view. ![SourceLoop Traffic dashboard showing the metric tiles, time-series chart, attribution model selector, and channel breakdown table](/help/screenshots/sourceloop-traffic-dashboard-chart.webp) ## Step 1: Pick your filters The filter bar at the top of the dashboard controls every number on the page. 1. **Date range** — defaults to the last 30 days. Click the date picker to pick a preset (Last 7 days, Last 90 days, This month, Custom). Every metric, the trend chart, and the table all update. 2. **Conversion type** — "All conversion types" by default. Click the dropdown to scope the dashboard to just form submissions, just meetings, just chats, just payments, or any combination. Useful for separating lead generation (forms) from revenue (payments). 3. **Attribution model** — defaults to "Last touch". Click to switch between the seven models (covered in detail below). 4. **Filter** — add ad-hoc filters on UTM source, medium, campaign, device, country, etc. Multiple filters AND together. The dashboard recomputes the entire view on every filter change, no separate apply button. ## Step 2: Read the metric tiles Six metric tiles run across the top of the dashboard, each showing the current period total plus a percent-change vs. the previous period: - **Visitors** — unique anonymous identifiers in the period - **Sessions** — separated visits (a new session starts after 30 minutes of inactivity or a new referrer) - **Pageviews** — total page loads - **Conversions** — the count of conversion events matching the filter - **Conv. Rate** — Conversions ÷ Sessions, as a percentage - **Revenue** — sum of attributed revenue when payment integrations are connected A red arrow means the metric dropped vs. the previous period; green means it grew. Click any tile to add it to the trend chart below. ## Step 3: Read the trend chart The chart shows the selected metrics over time, with adjustable granularity: - **Daily** (the default; best for short ranges like 7-30 days) - **Weekly** (best for 60-90 day ranges) - **Monthly** (best for quarterly and year-long ranges) Two metrics can be plotted simultaneously on the same chart with a dual y-axis, the first metric is the bar chart (left axis), the second is the line chart (right axis). In the screenshot above, **Visitors** (orange bars) is plotted with **Conversions** (red line), so you can see directly whether a traffic spike turned into conversions or just bounced. To swap the plotted metrics, click the metric tiles. A green check next to a tile means it's currently on the chart. ## Step 4: Read the breakdown table Below the chart, a breakdown table groups the same data by dimension. Tabs at the top let you switch the grouping: - **Channel** — high-level bucket (Paid Search, Organic Social, Direct, Email, Paid Social, Referral, Display, etc.). Channels are defined by rules in Setup -> Channel rules. This is the default tab. - **Source** — literal `utm_source` value (google, linkedin, twitter, newsletter, etc.) - **Medium** — literal `utm_medium` value (cpc, organic, social, email, etc.) - **Campaign** — literal `utm_campaign` value (e.g., "summer-sale-2026") - **UTM Term** — `utm_term` (commonly keywords from paid search) - **UTM Content** — `utm_content` (commonly ad-variant identifiers) - **Referrer** — the referring URL (referer header), useful for spotting referral traffic from specific sites Each row shows the same six metrics from the tiles above, scoped to that group. ![Traffic dashboard table view with rows for paid_search, social, referral, email, paid_social, and conversion counts highlighted in orange](/help/screenshots/sourceloop-traffic-dashboard-table.webp) The conversion column is highlighted in orange so it's easy to spot the highest-converting groups even with many rows. Click any row to drill into that group, the filter bar updates to include the row's value, and the entire dashboard recomputes. Useful for asking "which campaigns inside Paid Search actually convert?" — click into Paid Search, then switch to the Campaign tab. ## Step 5: Switch visualisations The same table data can be rendered three ways. Use the icons in the top-right of the table view to toggle: - **Table** — precise numbers, easy to export, best for many rows - **Bar chart** — magnitude comparison, best for 5-15 rows - **Pie chart** — relative share, best for 5-7 categories - **Donut chart** — similar to pie but with a hole in the middle, sometimes easier to read for stacked categories ![Visualization toggle showing icons for table, bar chart, horizontal bar chart, and pie/donut chart in the top-right of the Traffic dashboard](/help/screenshots/sourceloop-traffic-dashboard-visualizations.webp) The chart at the top of the dashboard (time-series) and the breakdown view (table/bar/pie) are independent, you can have a time-series chart at the top and a pie chart at the bottom in the same view. ## Step 6: Switch attribution models This is the most powerful feature of the dashboard, and the one most teams don't use enough. The attribution model selector top-right (showing "Last touch" by default) controls how conversion credit gets assigned to channels and sources when a visitor has touched more than one. SourceLoop has seven models: - **Last Touch** — 100% credit to the converting session's channel - **First Touch** — 100% credit to the original acquisition channel - **Last Non-Direct** — 100% to the last non-direct channel before conversion (the default; matches Google Analytics) - **First Non-Direct** — 100% to the first non-direct channel in the journey - **Linear** — equal credit split across every touchpoint in the journey - **Position-Based (U-Shaped)** — 40% to first, 40% to last, 20% split across the middle - **Time Decay** — exponential decay with a 7-day half-life; recent touches get more credit To compare models, click the model selector and pick a second model. Both appear as separate columns in the table and overlaid lines in the chart. Useful for spotting the difference between top-of-funnel acquisition channels (which dominate under First Touch) and closing channels (which dominate under Last Touch). For more detail on each model, see [Attribution models overview](/help/types-of-attribution-models/). ## Step 7: Export the data Top-right of the table, click **Export** to download the visible table as CSV. The export honours every filter you've applied including conversion-type scope, attribution model, and date range, so the spreadsheet matches exactly what's on screen. Useful when feeding the data into a separate BI tool, sending to your accountant, or sharing with someone who doesn't have a SourceLoop login. ## What's next - **Compare attribution models in detail:** [Attribution models overview](/help/types-of-attribution-models/) - **Drill into landing page performance:** [Content dashboard](/help/content-dashboard/) - **Look at where in the world your traffic comes from:** [Locations dashboard](/help/locations-dashboard/) - **See which devices convert best:** [Devices dashboard](/help/devices-dashboard/) - **Read pre-conversion browsing patterns:** [Paths dashboard](/help/paths-dashboard/) - **Connect spend to revenue:** [Ads Performance dashboard](/help/ads-performance-dashboard/) ## Frequently Asked Questions ### What's the difference between Channel, Source, and Medium tabs? Channel is the high-level bucket (Paid Search, Organic Social, Direct, Email, etc.), derived from your rules in Setup -> Channel rules. Source is the literal `utm_source` value (e.g., google, linkedin, twitter). Medium is the `utm_medium` value (e.g., cpc, organic, social, email). Channel rolls up Sources and Mediums into named groups for cleaner reporting; the tabs let you drill from the bucket down to the specific tag. ### How do I compare two attribution models side by side? Click the attribution model selector top-right, currently shown as "Last touch". Pick a second model from the dropdown to add it as a separate column. You can stack multiple, Last Touch + First Touch + Linear, in the same table to see how conversion credit shifts across models. The chart on the same page updates to show overlaid lines per model. ### Why do my numbers change when I switch attribution models? Because the model decides how to assign credit. Last Touch gives 100% to the converting session. First Touch gives 100% to the original session. Linear splits evenly across every session. Time Decay weights recent sessions higher. A campaign that looks weak under Last Touch may dominate under First Touch, that's the value of comparing them. ### Can I filter to just specific conversion types? Yes. The "All conversion types" dropdown top-right lets you scope the dashboard to just form submissions, just meetings, just chats, just payments, or any combination. Useful when you want to see channel performance specifically for revenue (filter to payments) vs lead generation (filter to forms). ### What does the bar chart vs pie chart vs table toggle do? It just changes how the same data is displayed. Table is best for precise numbers and exporting. Bar chart is best for comparing magnitudes across many channels. Pie chart works when you have 5-7 channels and want to see relative share at a glance. All three are available in the top-right of the table view. ### My channels show up as 'Direct' for traffic I expected to be attributed. Why? The Direct bucket is the catch-all for sessions where the channel detection couldn't find a clean signal, no UTM parameters, no recognised referrer, no click ID. Common causes, your campaign URL isn't tagged with UTMs, the visitor cleared cookies and returned later, or they came from an iOS app where the referrer is stripped. Filter the table by Direct to see the landing pages those visitors hit and decide if you need to add UTMs to a specific campaign. ### How fresh is the data in the dashboard? Sessions, pageviews, and conversions appear within seconds of happening. Revenue arrives within minutes of the payment webhook from your payment integration. Ad spend and impressions refresh once daily at 05:00 UTC (covering the last 14 days to catch late-attribution updates from the ad platforms). --- # How to Read the SourceLoop Content Dashboard Walk through the Content dashboard, the landing-page attribution view that shows which pages actually drive conversions and revenue, not just pageviews. Source: https://sourceloop.ai/help/content-dashboard/ Updated: 2026-05-28 --- The **Content dashboard** is the landing-page-focused view of SourceLoop's attribution. It answers the questions Google Analytics doesn't: not just "which pages got the most traffic" but "which pages actually drove conversions and revenue". If the [Traffic dashboard](/help/traffic-dashboard/) tells you which **channels** brought visitors, the Content dashboard tells you which **pages on your site** turned those visitors into leads and customers. ## What the Content dashboard answers In one screen: - **Which landing pages bring in your best traffic** (conversion rate, not just volume) - **Which blog posts pull visitors who eventually convert** (under First Touch) - **Which page is closing deals at the end of the journey** (under Last Touch) - **How much revenue is attributable to each page** (when payment integrations are connected) - **Where you're spending content effort that isn't paying off** (high traffic, low conversion) It's the right dashboard to use for content marketing planning, SEO investment decisions, and CTA-placement experiments. ![SourceLoop Content dashboard showing metric tiles, time-series chart, and the landing page breakdown](/help/screenshots/sourceloop-content-dashboard-chart.webp) ## Step 1: Pick your filters Same filter bar as the rest of the dashboards: 1. **Date range** — defaults to the last 30 days 2. **Conversion type** — all conversions, or scope to forms / meetings / chats / payments 3. **Attribution model** — defaults to Last Touch; switch to compare how page credit shifts 4. **Filter** — add ad-hoc filters on URL, channel, device, country, etc. For content marketing analysis specifically, two filters are particularly useful: - **Filter by URL contains /blog/** to look only at blog post performance - **Filter by Channel = Organic Search** to see SEO landing page performance separately ## Step 2: Read the metric tiles Six tiles run across the top, scoped to whatever filters you've applied: - **Visitors** — unique anonymous identifiers in the period - **Sessions** — separated visits - **Pageviews** — total page loads (this metric becomes especially relevant on a content dashboard) - **Conversions** — the count of conversion events matching the filter - **Conv. Rate** — Conversions ÷ Sessions, as a percentage - **Revenue** — sum of attributed revenue when payment integrations are connected Each tile shows a percent change vs the previous period. Click a tile to toggle it on the time-series chart below. ## Step 3: Read the trend chart The trend chart shows your selected metrics over time, in Daily / Weekly / Monthly granularity. Useful for content marketing specifically because: - **Spotting content launch effects** — did publishing that pillar post on Tuesday pull a measurable lift in conversions? - **Detecting SEO drift** — is a page that used to convert well slowly losing conversion rate (often a sign of ranking decay or stale content)? - **Comparing campaign timing** — when did the content + paid promo combo land best on a calendar? Just like the Traffic dashboard, you can plot two metrics simultaneously on dual axes (bar for one, line for the other). ## Step 4: Read the landing page breakdown The table below the chart is where the real Content dashboard insight lives. Each row is a URL on your site, with the same six columns as the metric tiles. ![Content dashboard landing page table with rows showing slash, blog ai mode optimisation, blog tracking utm parameters, blog grok rank tracking tools, all with visitor and conversion counts](/help/screenshots/sourceloop-content-dashboard-table.webp) The default tab is **Landing Page**, the URL where a session first arrived. Tabs at the top let you switch: - **Landing Page** — the entry-point URL of each session - **Visited Page** — any page viewed during sessions (including non-entry views) The two tabs answer different questions: - "What's pulling visitors in?" → Landing Page tab - "What are they reading once they're here?" → Visited Page tab A high-traffic homepage might dominate Landing Page (most direct + brand visitors enter there) but be barely visible on Visited Page (people land on the homepage and bounce). A pricing page might be the opposite, very few people land on it directly, but a lot view it later in the session, and the conversions correlate with those views. ## Step 5: Spot the patterns that matter A few useful reading patterns once the data is on screen: **High traffic, low conversion** → the page brings visitors but the wrong ones, or the CTA isn't matched. Action: rewrite the CTA, or add a more targeted lead magnet for this audience. **Low traffic, high conversion** → the page is excellent but underexposed. Action: promote it more (paid, social, internal linking, SEO refresh). **High traffic and high conversion** → your flagship content; keep updating it, and study it to inform future content. **Big gap between Last Touch and First Touch numbers for the same page** → switch attribution models in the model selector to compare. A blog post that scores low under Last Touch (no one converts immediately after reading it) but high under First Touch (it's many visitors' original entry point) is a top-of-funnel asset. Don't kill it; just measure it correctly. ## Step 6: Switch visualisations Same toggle as the Traffic dashboard, top-right of the table area, Table / Bar / Pie / Donut. For Content specifically: - **Table** is the workhorse, especially when comparing 20+ pages by conversion rate - **Bar chart** is great for "top 10 landing pages by conversion rate" presentations to stakeholders - **Pie chart** isn't usually useful here because content tends to have a long tail (one homepage + many blog posts), pie charts compress badly above ~7 slices ## Step 7: Switch attribution models The model selector top-right works the same way as on the Traffic dashboard, defaults to Last Touch; switch (or stack multiple) to compare. Two model switches that are particularly useful for content analysis: - **First Touch vs Last Touch** — top-of-funnel acquisition assets dominate under First Touch; closing pages dominate under Last Touch. Comparing both reveals your full funnel. - **Time Decay** — for content with a long consideration cycle (B2B SaaS, agencies, high-ticket ecommerce), Time Decay weights recent touches more heavily. Useful when a long lead-time blog post deserves credit for the eventual conversion months later. For deeper detail on each model, see [Attribution models overview](/help/types-of-attribution-models/). ## Step 8: Export and act Top-right of the table, click **Export** to download the visible table as CSV with every filter and the current attribution model applied. Typical follow-up actions teams take after a Content dashboard session: - **SEO refresh shortlist** — pages with declining conversion rate over the period; prioritise rewrites - **Content distribution plan** — high-conversion-rate, low-traffic pages; promote via newsletter and paid - **Internal linking opportunities** — top-converting pages should be linked to from your homepage and high-traffic blog posts - **Discontinue list** — high-effort content with consistently low traffic and low conversions; cut the maintenance burden ## What's next - **See where traffic comes from by channel:** [Traffic dashboard](/help/traffic-dashboard/) - **See where your visitors are geographically:** [Locations dashboard](/help/locations-dashboard/) - **See device-level performance:** [Devices dashboard](/help/devices-dashboard/) - **See pre-conversion path patterns:** [Paths dashboard](/help/paths-dashboard/) - **Compare attribution models in detail:** [Attribution models overview](/help/types-of-attribution-models/) ## Frequently Asked Questions ### What's the difference between Landing Page and Visited Page? Landing Page is the URL where the visitor first arrived in that session (their entry point). Visited Page is any page they viewed during the session. A blog post can be both, if the visitor entered the site on that blog post (landing) and also browsed it later from the homepage (visited). The two tabs let you separate "what pulled them in" from "what they consumed". ### Why does a landing page show high traffic but no conversions? Three usual reasons. (1) Top-of-funnel content (a blog post answering a basic question), pulls visitors who aren't ready to buy yet. (2) Bad call-to-action match, the page brings the wrong audience or no CTA at all. (3) Wrong attribution model for this question, switch to First Touch to see if the page drives initial visits that convert later via a different page. Both stories matter; the dashboard lets you tell them apart. ### Can I exclude internal pages like /admin or /preview from the report? Yes. Use the Filter button top-left to add a "page URL does not contain" filter for the paths you want to hide. The filter persists for your session, so once set, every tab on the dashboard respects it. ### How does this differ from Google Analytics' Top Landing Pages report? Google Analytics counts sessions per landing page; SourceLoop counts sessions, conversions, conversion rate, and attributed revenue per landing page. The conversion column is the key difference, GA only shows traffic, SourceLoop shows whether that traffic actually became leads or customers. Plus you get attribution model switching, which GA doesn't do. ### Can I see what content visitors viewed before they converted? Use the Visited Page tab on this dashboard to see the conversion contribution of every page (not just entries). For a per-contact view of which pages were viewed in sequence, open a contact in the Contacts Hub. ### My homepage shows up as the top landing page. Is that a problem? Not necessarily. For most B2B sites, the homepage is the largest source of session entries because direct traffic, brand search, and the canonical URL all funnel there. What matters is the conversion rate. A homepage with 10% conversion is great; a blog post with 0.5% conversion may need work even if it has more visits. ### How fresh is the data? Same as the rest of the SourceLoop dashboards, sessions and conversions appear within seconds of happening, revenue arrives within minutes, ad spend refreshes once daily at 05:00 UTC. --- # How to Read the SourceLoop Locations Dashboard A walkthrough of the Locations dashboard, showing which countries, regions, and cities convert into leads, meetings, chats, and revenue. Source: https://sourceloop.ai/help/locations-dashboard/ Updated: 2026-05-28 --- The **Locations dashboard** is SourceLoop's geographic attribution view. It groups every session and conversion by the visitor's country, region (state or province), or city, so you can see where your most valuable audiences actually live. If the [Traffic dashboard](/help/traffic-dashboard/) tells you which channels brought visitors and the [Content dashboard](/help/content-dashboard/) tells you which pages they landed on, the Locations dashboard tells you the geography behind both, useful for international expansion decisions, geo-targeted ad spend, and country-specific content investment. ## What the Locations dashboard answers In one screen: - **Which countries are your top conversion markets** (by count and by rate) - **Whether you're under- or over-spending in a specific country** relative to its conversion volume - **Which regions inside a country drive the most leads** (e.g., is your California-targeting paid social actually pulling LA + SF, or one of them?) - **Which cities have the highest revenue concentration** (when payment integrations are connected) - **Where your conversion mix changes** (a country where chat dominates vs. one where forms do) It's the right dashboard for international expansion planning, geo-targeted ad campaign decisions, and figuring out whether a specific market deserves localised content. ![SourceLoop Locations dashboard showing a stacked bar chart of Conversions by Country with breakdowns by Web Form, Meeting, and Chat](/help/screenshots/sourceloop-locations-dashboard-chart.webp) ## Step 1: Pick your filters Same filter bar as every other dashboard: 1. **Date range** — defaults to the last 30 days 2. **Conversion type** — all conversions, or scope to forms / meetings / chats / payments 3. **Attribution model** — defaults to Last Touch 4. **Filter** — add ad-hoc filters on channel, source, campaign, device, country, etc. For geographic analysis specifically, two patterns are particularly useful: - **Filter by Channel = Organic Search** to see which countries find you via SEO - **Filter by Channel = Paid Search or Paid Social** to see whether your paid spend is reaching the geographies you targeted ## Step 2: Read the stacked-bar chart Unlike the Traffic and Content dashboards (which show a time-series chart at the top), the Locations dashboard leads with a **stacked bar chart grouped by location**. Each bar is a country (or region or city, depending on tab), and the bar is segmented by conversion type, Web Form, Meeting, Chat, Payment. This view is the fastest way to spot a few things: - **Which countries dominate your conversion volume** at a glance (the tallest bars) - **Whether the conversion mix shifts by country** (e.g., the US is heavily Web Form, while the UK has a higher proportion of Meeting bookings, which usually means a different sales motion is working there) - **Where the long tail starts** (after the top 5-10 countries, do you have meaningful volume or is it noise?) Above the chart, the metric selector lets you swap what's being plotted. Defaults to **Conversions by Country**; switch to: - **Visitors by Country** — raw audience size - **Sessions by Country** — visit volume - **Pageviews by Country** — content consumption - **Revenue by Country** — dollar-weighted, when payment integrations are connected - **Conv. Rate by Country** — efficiency rather than volume To the right of the metric selector, three icons let you flip between **bar chart**, **horizontal bar chart**, and **pie / donut** visualisations of the same data. ## Step 3: Switch grouping with the tabs Below the chart, the table area has three tabs: - **Country** — the default; ISO country code with flag emoji - **Region** — state or province inside a country (e.g., California, Ontario, Bavaria) - **City** — major metros (e.g., London, New York, Sydney) The chart at the top of the dashboard updates to match whichever tab you've selected. So picking the **Region** tab shows a bar chart of conversions by region, and so on. ![Locations dashboard table view with Country tab selected, showing US, GB, CA, DE, AU, IN, BR, FR, JP, NL with visitor, session, pageview, conversion, and conversion rate columns](/help/screenshots/sourceloop-locations-dashboard-table.webp) The table itself shows the same six metric columns as every other dashboard, scoped to the location: - **Visitors** - **Sessions** - **Pageviews** - **Conversions** (highlighted in orange so it's easy to spot at a glance) - **Conv. Rate** - **Revenue** (when payments are connected) Country rows have a flag emoji prefix for instant recognition; region rows show the region name with the parent country flag; city rows show the city name with country flag. Click any row to drill in. The filter bar updates to scope the entire dashboard to that location, so clicking on the **US** row in the Country tab and then switching to the **City** tab shows just US cities. ## Step 4: Compare two periods The **Compare** toggle in the top-right of the table area (next to the visualisation icons) adds a comparison column showing the same metric for the previous period. Useful for: - **Tracking geo-expansion** — did the country you opened a new market in actually grow conversions this period vs last? - **Spotting decline early** — a country whose conversions dropped 30% period-over-period is worth investigating before it's a year-over-year problem - **Validating geo-targeting changes** — did tightening your Google Ads targeting to North America actually reduce non-target conversions as planned? ## Step 5: Switch attribution models The model selector top-right works the same as on the other dashboards. For geographic analysis specifically, two model switches are particularly revealing: - **First Touch by Country** shows you which countries originally discovered you. Often differs from Last Touch significantly, especially if you have country-specific brand awareness from press, partnerships, or word-of-mouth. - **Linear by Country** distributes credit across all touches. Useful for countries with long sales cycles where last-touch conversion is in a major hub city but earlier touches happened elsewhere in the country. See [Attribution models overview](/help/types-of-attribution-models/) for detail on each model. ## Step 6: Spot patterns that matter Useful patterns to look for once data is on screen: **Top country dominates volume but has below-average conversion rate** → you have product-market fit there but you're attracting non-ideal visitors. Action: narrow paid targeting, refresh top-of-funnel content to qualify earlier. **Top country dominates volume AND has above-average conversion rate** → your flagship market. Action: invest more here, this is where every additional marketing dollar pays back best. **Small country with very high conversion rate** → an under-invested market with real intent. Action: dedicate a country-specific ad campaign with locale-relevant copy; consider language localisation if the rate is significantly above average. **Mismatch between Country tab and Region tab** → e.g., US is your top country, but the Region tab shows all the conversions concentrated in two states (California + New York). Action: targeted Region-level campaigns rather than country-wide. **Conversion mix differs by country** → a country where Meeting is much higher than Web Form usually means a different sales motion is winning there. Action: replicate the meeting-booking placement and CTA on equivalent country pages. ## Step 7: Export and act Top-right of the table, click **Export** to download the visible table as CSV with current filters and attribution model. Typical follow-ups: - **Geo-targeting refinement** — copy the top-converting country list directly into your Google Ads or Meta targeting - **Localisation prioritisation** — top countries with above-average rates and low share of pages translated → priority list for localisation - **Sales territory planning** — top cities by revenue → priority cities for AE territory assignment - **Currency / pricing experiments** — countries with high traffic but low conversion rate are candidates for local-currency pricing tests ## What's next - **See which channels drove your traffic:** [Traffic dashboard](/help/traffic-dashboard/) - **See which pages on your site converted:** [Content dashboard](/help/content-dashboard/) - **See which devices and browsers convert best:** [Devices dashboard](/help/devices-dashboard/) - **See pre-conversion path patterns:** [Paths dashboard](/help/paths-dashboard/) - **Compare attribution models:** [Attribution models overview](/help/types-of-attribution-models/) ## Frequently Asked Questions ### How does SourceLoop detect the visitor's location? By IP address at the moment the visitor's session starts. The IP is resolved to a country, region (state or province), and city using a standard geo-IP database. This works without asking the visitor for permission, no GPS or location-prompt is involved. Accuracy is high at the country level, very high at the region level for most countries, and good at the city level for major metros. ### Are VPN and proxy users counted at the wrong location? Yes, that's an inherent limit of IP-based geolocation. A US user on a UK VPN looks like a UK visitor to SourceLoop, the same way they do to Google Analytics or any other web analytics tool. For B2B audiences with corporate VPNs, this is worth keeping in mind, your "London" cluster may include some New York employees routing through a London office gateway. ### My conversions in Country X look high but the conversion rate is low. What does that mean? Volume vs efficiency. A high-volume, low-rate country usually means you're getting lots of low-intent traffic from there (often via paid social, organic search, or trending content), but the visitors aren't your ideal customer profile. Action, either tighten your geo-targeting on paid campaigns, or improve the country-specific landing page if the audience is genuinely valuable but the page isn't converting them. ### Should I run separate campaigns per country? That's the tactical question this dashboard helps you answer. Look at the conversion rate column. Countries with a meaningfully different conversion rate from the average usually justify their own targeting (separate ad campaign with locale-specific landing pages, currency, language). Countries with rates close to the average can often share campaigns. ### Can I see breakdowns smaller than city, like postal code? Not from IP geolocation alone, IP databases don't reliably resolve to postal code granularity. If you collect postal codes in your forms, those values are stored on the contact in the Contacts Hub and you can group on them there. The Locations dashboard itself stops at city. ### Why doesn't the dashboard show a map? It's a visualisation choice. Maps look good but read poorly for actual analysis, you can't easily compare conversion rate between Belgium and Argentina on a map. The table and bar chart show the same data in a form that's faster to scan and easier to act on. We may add a map view later for users who want it. ### How fresh is the data? Sessions and conversions appear within seconds, revenue arrives within minutes of the payment webhook. The geo-IP database is updated weekly; new IP allocations may take 1-2 weeks to attribute to the correct country. --- # How to Read the SourceLoop Devices Dashboard The Devices dashboard, see whether mobile, desktop, or tablet visitors convert better, and which browsers and operating systems they use. Source: https://sourceloop.ai/help/devices-dashboard/ Updated: 2026-05-28 --- The **Devices dashboard** is SourceLoop's device-and-browser attribution view. It groups every session and conversion by device type (mobile, desktop, tablet), by browser, or by operating system, so you can see where your audience actually converts and where your investment in mobile-first design or desktop-only flows is paying off. If the [Traffic dashboard](/help/traffic-dashboard/) tells you which channels brought visitors and the [Content dashboard](/help/content-dashboard/) tells you which pages they landed on, the Devices dashboard tells you the surface they were on when they converted. ## What the Devices dashboard answers In one screen: - **Whether mobile, desktop, or tablet drives more conversions** (by volume and by rate) - **Which browser converts at the highest rate** (Chrome, Safari, Firefox, Edge, etc.) - **Whether iOS or Android visitors are more valuable**, when revenue integrations are connected - **Where your mobile experience is leaking** (high mobile traffic + low mobile conversion rate) - **Which device delivers your most expensive conversions** vs your easiest ones It's the right dashboard for mobile UX investment decisions, browser-specific ad creative testing, and prioritising which device's checkout flow to refactor next. ## Step 1: Pick your filters Same filter bar as the rest of the dashboards: 1. **Date range** — defaults to the last 30 days 2. **Conversion type** — all conversions, or scope to forms / meetings / chats / payments 3. **Attribution model** — defaults to Last Touch; switch (or stack two) to compare 4. **Filter** — add ad-hoc filters on channel, source, campaign, country, etc. For device analysis specifically, two patterns are particularly useful: - **Stack Last Touch + First Touch attribution** — this reveals cross-device journeys. If Mobile dominates First Touch but Desktop dominates Last Touch, your audience discovers you on a phone and converts on a laptop. Common for B2B; rarer for consumer products. - **Filter by Channel = Paid Social** — Paid Social skews very mobile-heavy. The Devices dashboard scoped to Paid Social tells you whether your mobile landing pages are converting that audience or just collecting taps. ## Step 2: Read the donut chart Unlike the Traffic and Content dashboards (which show a time-series chart at the top), the Devices dashboard leads with a **donut chart showing the share of conversions by device type**. The three segments are Desktop, Tablet, and Mobile, with the total in the centre. This view answers one question fast: where is most of your conversion volume actually coming from? If the donut is heavily skewed one way (e.g., 70% Mobile), that's where to invest design and testing effort first. Above the donut, the metric selector lets you swap what's being plotted, switch from Conversions to Visitors, Sessions, Pageviews, Conv. Rate, or Revenue. To the right of the metric selector, three icons flip between **bar chart**, **horizontal bar chart**, and **pie / donut** visualisations. The bar-chart variation is especially useful when comparing two attribution models side by side, the chart will show two bars per device type (one per model) with the conversion-type breakdown stacked inside each. ## Step 3: Switch grouping with the tabs The table area below the chart has three tabs: - **Device Type** — Mobile, Desktop, Tablet (the default) - **Browser** — Chrome, Safari, Firefox, Edge, Opera, Samsung Internet, etc. - **OS** — iOS, Android, macOS, Windows, Linux, ChromeOS, etc. The chart at the top updates to match the selected tab. ![SourceLoop Devices dashboard with donut chart at top showing Desktop, Tablet, Mobile share, Device Type tab selected, and table showing Mobile, Desktop, Tablet with Visitors, Sessions, Pageviews, Conversions, and Conv. Rate columns](/help/screenshots/sourceloop-devices-dashboard-table.webp) Each row shows the same six metric columns as the other dashboards, scoped to the device segment: - **Visitors** with period-over-period change - **Sessions** with period-over-period change - **Pageviews** with period-over-period change - **Conversions** highlighted in orange - **Conv. Rate** as a percentage - **Revenue** when payment integrations are connected The **Compare** toggle on the top-right of the table area adds a period-over-period comparison column so you can see whether mobile conversions grew or shrunk vs the previous period. ## Step 4: Spot patterns that matter A few useful reading patterns once data is on screen: **Mobile dominates volume but underperforms on conversion rate** → your mobile experience has friction. Form length, page speed, tap targets, viewport rendering — pick one and run a usability test. Common gap: 30-50% lower conversion rate on mobile vs desktop, which is often fixable with form simplification. **Desktop dominates conversion rate, Mobile dominates pageviews** → classic browse-on-phone-buy-on-laptop pattern. Action: don't optimise the mobile site to "convert"; optimise it to capture the email so you can retarget the visitor across devices. Lead with newsletter signups, sample requests, or saved-cart prompts instead of full checkouts. **Safari converts at meaningfully higher rate than Chrome** → your audience skews Apple, which usually correlates with higher willingness-to-pay. Worth a creative test: do iOS visitors see the higher-priced plan first? **iOS conversion rate is meaningfully higher than Android** → similar story to Safari vs Chrome. Use the OS tab to confirm. If true, your bidding on Google Ads and Meta can lean more aggressively on iOS. **A specific browser (e.g., Edge) shows surprisingly high conversion** → often a B2B signal (corporate Windows users default to Edge). Worth checking whether your B2B campaigns are reaching the right audience. ## Step 5: Compare attribution models The model selector top-right is one of the most useful features on this dashboard for understanding cross-device behaviour. The screenshot below the Filter bar shows what comparing two models at once looks like: when you add a second model (e.g., First Touch alongside Last Touch), the bar chart shows two bars per device type, one per model. The difference is a direct visualisation of cross-device journeys. If Mobile is significantly taller on the First Touch bar than the Last Touch bar (and Desktop is the opposite), that's confirmation that your audience starts on a phone and finishes on a laptop. The size of the gap tells you how often. For more on each model, see [Attribution models overview](/help/types-of-attribution-models/). ## Step 6: Switch visualisations Same toggle as the other dashboards, top-right of the table area, Table / Bar / Pie / Donut. For Devices specifically: - **Donut chart** is the natural fit when you have 3-4 device types; instantly readable - **Bar chart** is best when comparing two attribution models side by side - **Pie chart** is functionally identical to Donut; pick whichever you prefer visually - **Table** is best when you need precise numbers across many browsers (Browser tab can have 10+ rows) ## Step 7: Export and act Top-right of the table, click **Export** to download the visible table as CSV. Typical follow-up actions: - **Mobile UX investment prioritisation** — if Mobile has 60% of traffic and 40% of conversions, that's the next quarter's biggest design opportunity - **Browser-specific A/B testing** — if Safari converts at 1.3x Chrome, set up a test on Chrome that mimics Safari's conversion flow - **Cross-device retargeting strategy** — if First Touch ≠ Last Touch device, build a list of mobile-discovered contacts to retarget on desktop placements - **Bid adjustment** — feed the Device-segment conversion data into Google Ads and Meta as device bid adjustments ## What's next - **See which channels drove the traffic:** [Traffic dashboard](/help/traffic-dashboard/) - **See which pages on your site converted:** [Content dashboard](/help/content-dashboard/) - **See where your traffic comes from geographically:** [Locations dashboard](/help/locations-dashboard/) - **See pre-conversion path patterns:** [Paths dashboard](/help/paths-dashboard/) - **Compare attribution models:** [Attribution models overview](/help/types-of-attribution-models/) ## Frequently Asked Questions ### How does SourceLoop classify a session as Mobile vs Desktop vs Tablet? Based on the user-agent string the browser sends with every request. SourceLoop parses it into one of three device categories using a standard device-detection library. Mobile covers smartphones, Desktop covers laptops and desktop computers, Tablet covers iPads, Android tablets, and similar form factors. Edge cases like foldables are categorised by the active screen size at session start. ### Why does Mobile show high traffic but low conversion rate? Common reason, mobile visitors have higher intent for short-form content (scrolling, browsing) and lower intent for longer-form actions (filling out a multi-field form, booking a meeting). If your Mobile conversion rate is meaningfully below Desktop, check the form length and friction on mobile, the speed of page load on slow connections, and whether your CTAs are tappable without zooming. Most rate gaps are UX problems, not audience problems. ### My Browser breakdown shows Chrome at 60-70%. Is that normal? Yes, very. Chrome's global market share is around 60-65% so most dashboards skew there. What's interesting is the conversion-rate column, if Safari converts 50% better than Chrome at the same volume, that's a signal that iPhone / Mac users are more valuable for your business (often the case for B2B and premium ecommerce). Use that to inform creative and bidding. ### What does the OS tab show that the Device Type tab doesn't? Operating system version, not just form factor. The Device Type tab tells you Mobile vs Desktop vs Tablet. The OS tab splits Mobile into iOS vs Android (and version), Desktop into macOS vs Windows vs Linux, Tablet into iPadOS vs Android tablets. Useful when iOS conversion rate differs meaningfully from Android, which is common in B2B SaaS (iOS users skew higher-income) and high-end ecommerce. ### Can I see conversion data for a specific OS version, like Safari on iOS 17? Not in the standard view, the OS tab groups by OS family. For OS-version-level analysis, use the Filter button to add an explicit OS version filter, or export the raw data to CSV and pivot in a spreadsheet. ### How does this dashboard handle cross-device journeys? A single contact who visits on mobile, then converts on desktop will appear as a Mobile session AND a Desktop session in the table (each session counts under its own device), but as a single Conversion attributed under whichever attribution model you've picked. Under Last Touch, the Desktop session gets the conversion credit. Under First Touch, Mobile does. Switching models is the cleanest way to see how often cross-device behaviour happens. ### Why does Tablet sometimes have very low traffic in my dashboard? Tablet is genuinely the smallest segment for most audiences (5-10% of total traffic). For business audiences, it's usually closer to 2-5%. If your Tablet bar barely shows up, that's normal, the dashboard doesn't exclude or downplay it, your audience just doesn't use tablets much. --- # How to Analyse Conversion Paths and Customer Journeys The Paths dashboard, see the exact sequence of channels, sources, and pages every converting visitor went through before they converted. Source: https://sourceloop.ai/help/paths-dashboard/ Updated: 2026-05-28 --- The **Paths dashboard** is SourceLoop's multi-touch attribution view in its purest form. Instead of assigning credit to channels and summarising, it shows you the exact sequence of touches every converting visitor went through, the literal path to conversion. It's the dashboard for the question every marketer eventually asks: **"Is paid social actually driving sales, or just creating awareness that organic search converts later?"** The Paths view answers it directly. ## What the Paths dashboard answers In one screen: - **The single-touch paths that close in one visit** (high-intent traffic) - **The two- and three-touch paths that compound** (your real conversion funnel) - **Which channels reliably assist conversions** even when they're not the closer - **The exact sequence your highest-revenue customers go through** before they convert - **Whether your funnel has a repeating discovery → consideration → close pattern** (and what each role plays) It's the right dashboard when you need to defend or kill a channel based on its assist value, plan retargeting, or design a nurture sequence that mirrors real buyer behaviour. ![SourceLoop Paths dashboard showing the conversion path column with sequences like 1 social → 2 paid_search, 1 referral → 2 paid_search, 1 email → 2 paid_social → 3 paid_social, with Visitors, Conversions, and Revenue columns](/help/screenshots/sourceloop-paths-dashboard.webp) ## Step 1: Pick your filters The filter bar is similar to the other dashboards but with one new control specific to Paths: 1. **Date range** — defaults to the last 30 days 2. **Collapse repeat touches** — toggle in the top-right (covered in detail below); ON by default 3. **Filter** — add ad-hoc filters on channel, source, campaign, country, device, etc. The dashboard recomputes the entire view on every filter change. ## Step 2: Decide whether to collapse repeat touches The **Collapse repeat touches** toggle in the top-right is one of the most important controls on this dashboard. It changes what each path actually looks like: - **ON** (default) — repeated touches from the same channel collapse into one. A visitor who hits your site three times from social and converts shows up as `1 social` instead of `1 social → 2 social → 3 social`. Use this when you want to see the **structural** path: what unique sequence of channels matters. - **OFF** — every individual session is a separate touch. The same visitor shows as `1 social → 2 social → 3 social`. Use this when you specifically want to see **frequency** patterns: how many touches a channel needs before it converts. For most analysis (budget allocation, channel mix decisions), ON is the right default. Flip it OFF when investigating ad-frequency or nurture-cadence questions. ## Step 3: Switch grouping with the tabs Above the table, seven tabs control what each touch in the path represents: - **Channel** — high-level bucket (social, paid_search, referral, email, etc.) — the default - **Source** — literal `utm_source` value (google, linkedin, twitter, etc.) - **Medium** — literal `utm_medium` value (cpc, organic, social, email, etc.) - **Campaign** — literal `utm_campaign` value (e.g., "summer-sale-2026") - **Term** — `utm_term`, often keywords from paid search - **Content** — `utm_content`, often ad-variant identifiers - **Page** — landing pages on your site, so each touch is a specific URL instead of a channel The Page tab is especially powerful for content marketing analysis, it tells you the literal sequence of pages each converter read on the way to the conversion, like "1 /blog/utm-tracking → 2 /pricing → 3 /demo-thank-you". Switch tabs based on the question you're answering: - **"Which channel mixes convert?"** → Channel tab - **"Which specific Google Ads campaign features in winning paths?"** → Campaign tab - **"What content sequence do my best customers read?"** → Page tab - **"Which keywords appear in paid-search-assisted paths?"** → Term tab ## Step 4: Read the path rows Each row in the table is a unique path observed in the period, displayed as a sequence of numbered chips. Reading from left to right gives you the chronological order. Three columns: - **Visitors** — unique people who went through this exact path - **Conversions** — total conversion events from this path - **Revenue** — attributed revenue when payment integrations are connected Sort by Conversions to see the highest-volume paths first. That's where your real funnel structure lives. ## Step 5: Read the patterns that matter Once the table is sorted by Conversions (descending), look for these patterns: **Single-touch top rows** (`1 social`, `1 paid_search`, `1 referral`) → high-intent traffic that converts on first arrival. These are your best paid-acquisition opportunities, double down where the CPA is good. **Two-touch paths with the same closer** (`1 social → 2 paid_search`, `1 referral → 2 paid_search`) → the first channel is the discovery driver, the second is the closer. Paid search is doing a lot of "closing" work here; cutting it without thinking would crater your overall conversion rate, even though it's not the original source. **Repeat-channel paths** (`1 social → 2 social → 3 social`, with Collapse OFF) → social is doing both discovery and reinforcement. The channel needs frequency in your bidding strategy, that's a multi-impression brand-building motion, not a one-click direct-response motion. **Paths ending in Direct** (`1 paid_social → 2 organic_search → 3 Direct`) → classic awareness → consideration → close pattern. The earlier channels worked: brand recall closed the deal. Don't kill paid_social just because it's not in the last position; without it, the whole path wouldn't exist. **Email assists in the middle** (`1 social → 2 email → 3 paid_social`) → your nurture sequence is working. Email picked up an unconverted social visitor and re-engaged them before they converted via paid social. This is the kind of pattern that justifies investing in lifecycle marketing. ## Step 6: Compare paths under different attribution models Path data is structurally attribution-model-agnostic, the sequence itself doesn't change when you switch models. But the Visitors, Conversions, and Revenue columns do, because those numbers aggregate per the selected model. Two useful comparisons: - **Last Touch view** — counts conversions where the last touch in the path was the closer. Useful for understanding what closes. - **Linear or Position-Based view** — splits credit across every touch in the path. Useful for understanding total contribution, including assist value. For deep detail on each model, see [Attribution models overview](/help/types-of-attribution-models/). ## Step 7: Export and act Typical follow-up actions teams take after a Paths dashboard session: - **Budget defence** — if you're under pressure to cut paid social because it's not Last Touch, export the Paths data and show how often it appears in winning multi-touch sequences. The assist value is real and quantifiable. - **Retargeting design** — for paths where Direct closes, set up retargeting on the discovery channels (paid social, paid search) so brand awareness has somewhere to land - **Nurture sequence redesign** — paths where email middle-assists tell you which segments respond to email cadence; build sequences that mirror those paths - **Channel kill / keep decisions** — channels that never appear in any winning path can be cut without revenue impact (rare, but worth checking quarterly) - **Content sequence planning** — Page tab paths show the canonical "browsing journey" for your converters; use that to design internal linking, blog series, and CTA placement ## When to use Paths vs Traffic Quick reference: - **Traffic dashboard** — "Which channels drive conversions?" Budget allocation, channel-mix comparisons. - **Paths dashboard** — "What sequence of channels do my converters go through?" Multi-touch attribution defence, assist-channel decisions, nurture design. The two work together: Traffic tells you which channel **closes**, Paths tells you which channels **assisted** along the way. Most marketing-mix decisions need both views. ## What's next - **See channel-level conversion summaries:** [Traffic dashboard](/help/traffic-dashboard/) - **See landing page performance:** [Content dashboard](/help/content-dashboard/) - **See where converters live:** [Locations dashboard](/help/locations-dashboard/) - **See device-level conversion:** [Devices dashboard](/help/devices-dashboard/) - **Pick the right attribution model for your reporting:** [Attribution models overview](/help/types-of-attribution-models/) ## Frequently Asked Questions ### What's a 'conversion path'? The exact sequence of marketing touches a visitor went through before they converted. A single-touch path looks like 1 social, the visitor came from social and converted on the same session. A two-touch path looks like 1 social → 2 paid_search, they first arrived via social, came back later via paid search, and converted on the second visit. Paths can have 5+ touches for considered purchases. The dashboard shows every unique path observed plus the volume and revenue it produced. ### What does the 'Collapse repeat touches' toggle do? When ON (default), repeat touches from the same channel are collapsed into a single touch. A visitor who hits your site three times from social, then converts, shows up as 1 social (instead of 1 social → 2 social → 3 social). When OFF, every individual session is shown as a separate touch, which surfaces the real raw sequence including the repeat-engagement signal. Use ON for high-level analysis (which combinations matter), OFF when you specifically want to see frequency patterns. ### Should I read paths under Last Touch or First Touch attribution? Path data is naturally attribution-model-agnostic, the path is the path regardless of how you split credit. The model selector still affects the Visitors / Conversions / Revenue columns, though, those still aggregate per the model you've selected. For "which raw paths produce conversions", any model works. For "how much value to assign each path", the model matters. ### What does '1 social → 2 social → 3 social' mean? Three social visits before converting? Yes (with Collapse repeat touches OFF). It means the visitor came to your site three separate times from social before they converted on the third one. This pattern is common for considered purchases, social drives discovery and reinforcement before someone is ready to buy. Repeat-channel paths usually tell you which channel needs more frequency in your bidding strategy. ### Some paths end with 'Direct'. What does that mean? Direct is the catch-all when the channel detection couldn't find a signal, no UTM, no recognised referrer, no click ID. A path ending in Direct usually means the visitor remembered your URL from earlier sessions and typed it in (or bookmarked it). Common when the original Channel does its job, brand recall closes the deal. The path 1 paid_social → 2 organic_search → 3 Direct visible in the screenshot is a classic example, paid creates awareness, search confirms intent, direct closes. ### How is this different from the Traffic dashboard? Traffic dashboard tells you which channel produced each conversion (with credit assigned per the selected attribution model). Paths dashboard tells you the entire sequence of channels each visitor went through. Traffic answers "which channel converts"; Paths answers "which combinations of channels reliably convert". Use Traffic for budget allocation, Paths for understanding the funnel structure. ### I see hundreds of unique paths. How do I find what matters? Sort by Conversions (descending) to see your highest-volume paths first, those are the patterns your funnel actually runs on. Most teams find that 10-20 paths cover 80% of conversions. The long tail of single-occurrence paths is normal and not worth optimising individually. Focus on the top paths, then look for shared sequences (e.g., does every top path include paid_search at some position?). --- # How to Read the Ads Performance Dashboard Tie spend on Google Ads, Meta, TikTok, LinkedIn, and Microsoft Ads to real conversions and revenue in one cross-platform reporting view. Source: https://sourceloop.ai/help/ads-performance-dashboard/ Updated: 2026-05-28 --- The **Ads Performance dashboard** is SourceLoop's cross-platform paid-media view. It pulls spend and impressions from every connected ad platform (Google Ads, Meta, TikTok, Pinterest, LinkedIn, Microsoft / Bing) into one table and joins them against the conversions and revenue SourceLoop measured on your site. If the [Traffic dashboard](/help/traffic-dashboard/) tells you which channels brought visitors and the [Paths dashboard](/help/paths-dashboard/) tells you the conversion sequences they went through, the Ads Performance dashboard tells you whether your **paid acquisition is actually paying back**, with the most important metric for paid teams clearly visible in the top-right: ROAS. ## What the Ads Performance dashboard answers In one screen: - **Total ad spend across every platform** for the selected period - **Whether your ROAS is improving or declining** vs the previous period - **Which platform produces the best CPA** (spend per converted user) - **How platform-reported numbers compare to SourceLoop's measured truth** (attribution coverage) - **The week-by-week spend-to-conversion ratio** (is efficiency holding or slipping?) - **Drill-down to campaign / ad set / ad / keyword / audience** without leaving the dashboard It's the right dashboard for the weekly paid-media stand-up, the quarterly board update on marketing efficiency, and the "should we keep spending on TikTok?" debate. ## Step 1: Pick your filters The filter bar across the top: 1. **Currency** (USD by default in the screenshot) — pick the reporting currency you want everything converted into. Useful when you spend in multiple currencies; SourceLoop converts all rows to the same currency for like-for-like comparison. 2. **Filter** — add ad-hoc filters 3. **All platforms** — scope the dashboard to a single platform or a subset (just Google Ads + Meta, for example) 4. **Date range** — defaults to the last 30 days 5. **All conversion types** — scope to forms, meetings, chats, payments, or any combo For ROAS-focused analysis, filter to **payment conversions** specifically. That's the only conversion type that produces revenue, and ROAS becomes meaningful only when revenue is in the picture. ## Step 2: Read the metric tiles Six tiles run across the top, scoped to whatever filters you've applied: - **Spend** — total ad spend across all selected platforms (in your chosen reporting currency) - **Impressions** — total ads served, with period-over-period change - **Clicks** — total ad clicks, with period-over-period change - **CTR** — Clicks ÷ Impressions, the click-through rate - **Conversions** — SourceLoop-measured conversions from paid sessions - **ROAS** — Revenue ÷ Spend, expressed as a multiplier (6.77x means $6.77 of revenue per $1 spent) Each tile shows a percent-change arrow. Green up + ROAS rising → your paid acquisition is getting more efficient. Red down + ROAS dropping → time to investigate which platform or campaign caused the regression. ![SourceLoop Ads Performance dashboard showing metric tiles for Spend $23,901, Impressions 617.6K, Clicks 17.3K, CTR 2.80%, Conversions 867, ROAS 6.77x, plus a weekly bar chart with Spend (orange) and Conversions (blue) on dual axes](/help/screenshots/sourceloop-ads-performance-dashboard-chart.png) Click any tile to swap it onto the trend chart below. The chart supports two metrics simultaneously on dual y-axes, perfect for "spend (left axis) vs conversions (right axis)" comparisons. ## Step 3: Read the trend chart The chart shows your selected metrics over time, with Daily / Weekly / Monthly granularity. The default is Weekly, which is usually the right grain for ad performance, daily data is noisy, monthly hides timing. The dual-axis design is the key feature here. Plot Spend (orange bars, left axis) against Conversions (blue bars, right axis), and look for weeks where the two diverge: - **Spend rises but Conversions don't follow** → your auction prices climbed (or your creative fatigued) and you're spending more for the same outcome. Investigate which platform contributed. - **Conversions rise but Spend stays flat** → an efficiency win; your ROAS just got better. - **Both fall together** → seasonal dip, or you paused campaigns; spend cuts often produce proportional conversion cuts. ## Step 4: Read the platform breakdown table The table below the chart is the workhorse view. The default tab is **Platform**, showing one row per connected ad platform (Google Ads, Meta, TikTok, Pinterest, Microsoft / Bing, LinkedIn) with the full performance columns. ![Ads Performance dashboard table on the Platform tab showing Google Ads, Meta, TikTok, Pinterest, Microsoft/Bing, LinkedIn with Spend, Impressions, Clicks, CTR, CPC, CPM, and Conversions columns, plus 86% attribution coverage badge at top-right](/help/screenshots/sourceloop-ads-performance-dashboard-table.png) Columns split into two groups: **Platform reported** (data pulled from each platform's API): - **Spend** — what the platform charged you - **Impressions** — ads served - **Clicks** — ad clicks - **CTR** — click-through rate - **CPC** — cost per click - **CPM** — cost per 1,000 impressions **SourceLoop measured** (what actually happened on your site): - **Conversions** — the conversion count SourceLoop tied back to ad-driven sessions - **CPA, ROAS, revenue** when scrolled further right The split matters. Platforms over-report conversions (view-through, cross-device modelling, last-click priors). SourceLoop measures what actually happened in the browser of a visitor on your site. Comparing the two columns side by side reveals how much of each platform's "reported" performance is real on-site activity vs platform-side modelling. ## Step 5: Read the attribution coverage indicator Top-right of the table area, the **attribution coverage** indicator shows what percentage of platform-reported conversions SourceLoop was able to tie back to a real visitor session. The example shows 86%. How to read it: - **90%+ coverage** — excellent; the pixel is firing well across all paid landing pages - **70-90% coverage** — normal; small gaps from privacy settings, ad blockers, and consent banners - **Below 70% coverage** — investigate; the tracking pixel may not be installed on every ad landing page, or there's a CSP / cookie issue. Run the [Verify the SourceLoop tracking pixel is installed](/help/verify-tracking-is-working/) flow. Coverage is per-platform; click the badge to see which platforms have the lowest coverage. Often you'll find that TikTok or Pinterest is the lower number because their in-app browsers strip more identifiers, which is informative for what level of confidence to apply to each platform's conversion number. ## Step 6: Drill into Campaign, Ad Set, Ad, Keyword, Audience The seven tabs above the table let you drill from platform level down to the granularity that matters for your weekly decisions: - **Platform** — total per ad platform - **Campaign** — per-campaign performance across platforms - **Ad Set** (Meta) / **Ad Group** (Google) — the bidding level for most platforms - **Ad** — individual creative performance - **Keyword** — paid-search-specific keyword performance - **Audience** — Meta / LinkedIn audience-level performance Each tab inherits the filters and date range from the top of the page. Use the **Platform filter** in the top bar to scope to a specific platform when drilling into Campaign / Ad / Keyword, otherwise the table mixes campaigns across platforms. ## Step 7: Switch attribution models The model selector top-right works the same as on the other dashboards. The default is Last Touch, which matches how most ad platforms report. Two model switches that are particularly revealing for paid media: - **First Touch** — shows you which paid platform is the original discovery driver. Platforms that look weak on Last Touch but strong on First Touch are top-of-funnel assets. Cutting them based on Last Touch would crater your eventual conversion volume. - **Linear** — splits credit across every touch in the path. Useful for assigning fair "assist" credit to platforms that show up in winning multi-touch sequences (visible in the [Paths dashboard](/help/paths-dashboard/)) but never as the last click. The attribution model also affects how Conversions and ROAS columns aggregate, so a campaign's ROAS under Last Touch can look very different under Linear. Often the model that justifies paid spend is the multi-touch one. For deep detail on each model, see [Attribution models overview](/help/types-of-attribution-models/). ## Step 8: Spot patterns that matter A few useful reading patterns once data is on screen: **Highest spend ≠ highest ROAS** → your largest budget allocation isn't your most efficient channel. Look at the ROAS column. If a smaller-budget platform has higher ROAS, you have room to reallocate. **Very different platform-reported vs SourceLoop conversions** → the platform's modelling is overstating performance. Trust SourceLoop's number for budget decisions; use the platform's number only for bidding signal back to the platform. **Conversion volume up, but ROAS down** → you're growing absolute revenue but paying more per dollar of it. Common with aggressive scale-ups; whether it's worth it depends on margin and lifetime value. **Attribution coverage below 70% on a specific platform** → the SourceLoop tracker isn't firing reliably on that platform's landing pages. Fix tracking first before drawing conclusions. **A high-CTR but low-conversion campaign** → great hook, weak landing page. Compare the landing page on the Content dashboard. ## Step 9: Export and act Top-right of the table, click **Export** to download the visible table as CSV. Typical follow-ups: - **Weekly budget reallocation** — sort by ROAS, shift dollars from the bottom half toward the top half within your overall budget envelope - **Underperformer audit list** — campaigns with low conversion rate and high spend → either fix the landing page or pause - **Creative refresh queue** — ads with declining CTR over the period → schedule fresh creative - **Keyword cull** — paid-search keywords with high spend and low conversions → negative-keyword them or pause - **Coverage fix list** — platforms with low attribution coverage → check pixel placement on their landing pages ## What's next - **See where the traffic from those ads landed:** [Content dashboard](/help/content-dashboard/) - **See the full sequence of channels (paid + organic) that converted:** [Paths dashboard](/help/paths-dashboard/) - **See geographic performance of paid traffic:** [Locations dashboard](/help/locations-dashboard/) - **See device-level performance of paid traffic:** [Devices dashboard](/help/devices-dashboard/) - **Pick the right attribution model for ROAS reporting:** [Attribution models overview](/help/types-of-attribution-models/) ## Frequently Asked Questions ### What does '86% attribution coverage' mean in the top-right? It's the percentage of platform-reported conversions that SourceLoop was able to attribute to a visitor session on your site. Higher is better, 90%+ is excellent, 70-90% is normal, below 70% suggests pixel issues or tracking gaps. Coverage is per-platform and per-period; click the badge for a breakdown by platform showing which ones have the lowest coverage. ### Why are 'Platform reported' columns separate from SourceLoop's conversion column? Platform reported numbers come directly from each ad platform's API (spend, impressions, clicks, CTR, CPC, CPM, and the platform's own conversion count). SourceLoop's conversion column is what SourceLoop actually measured on your site. The two often differ, Google Ads might report 200 conversions, SourceLoop measures 180. The gap shows you where the platforms are over-counting (e.g., view-through conversions, cross-device modelling) vs what really happened on your site. ### What's the difference between Conversions and ROAS? Conversions is the count of events. ROAS (Return on Ad Spend) is the revenue divided by the spend, expressed as a multiplier (6.77x means $6.77 of revenue per $1 of ad spend). ROAS is the headline number for ecommerce; for B2B / SaaS, Conversions (or CPA, calculated from spend / conversions) usually matters more because the revenue happens months after the click. ### Why is Meta's CPC so much lower than Google's in my report? Different auction dynamics. Google Ads is intent-driven, the visitor typed something specific into search; CPCs are higher but conversion rate is also higher. Meta is interest-targeted, lower CPC but typically lower per-click conversion rate. Comparing the two on CPC alone is misleading; compare on CPA (spend per converted user) or ROAS to see which actually pays back. ### Can I filter this dashboard to one ad platform? Yes. Use the 'All platforms' dropdown next to the date picker to scope the dashboard to a single platform (just Google Ads, just Meta, etc.). Useful when you want to deep-dive without the table being cluttered by platforms you don't care about for this analysis. ### Why does the trend chart show two metrics on different axes? The dual-axis design lets you compare a spend metric (left axis) against a return metric (right axis) on the same chart. The screenshot shows Spend (orange bars, left axis) and Conversions (blue bars, right axis). Useful for spotting weeks where you spent more but didn't get proportionally more conversions, that's where efficiency is dropping. ### My LinkedIn shows very few conversions but high spend. What does that mean? LinkedIn has the highest CPCs in the industry by far, that's normal. The right comparison isn't conversions per dollar; it's conversion value per dollar. LinkedIn often produces fewer but higher-value B2B leads. To check, switch the dashboard view to a longer time range and look at LinkedIn's revenue / ROAS column instead of raw conversions. If revenue justifies spend, you're fine. If not, that's an investment to revisit. ### How fresh is the ad spend data? Ad spend and impressions refresh once daily at 05:00 UTC (with a 14-day re-sync window to catch the ad platforms' late attribution adjustments). Conversion data measured by SourceLoop appears within seconds. So spend is up-to-yesterday, conversions are up-to-this-second, the dashboard reconciles both per period you've picked. --- # How to Build and Analyse Conversion Funnels Build step-by-step conversion sequences, read the drop-off chart, compare two periods, and break down by channel, device, country, or attribution model. Source: https://sourceloop.ai/help/conversion-funnels-guide/ Updated: 2026-05-28 --- The **Funnels** feature in SourceLoop answers a question the dashboards can't: "Of the people who entered our pipeline at step A, how many actually reached step B, then C, then converted, and where exactly are we losing them?" The Traffic dashboard tells you channel totals. The Content dashboard tells you which pages got the most visits. **Funnels tell you the literal progression of visitors through a sequence of steps, and the percentage who drop out at each one.** This guide covers everything: how to build a funnel, how to read the three report views (Overview, Compare, Breakdown), the settings that change how steps are counted, and the common patterns marketers use them for. ## What a funnel actually is in SourceLoop A funnel is an ordered sequence of conditions. The classic four-step lead funnel looks like: | Step | Condition | |---|---| | 1 | Visitor lands on `/pricing` | | 2 | Visitor lands on `/signup` | | 3 | Visitor submits the signup form | | 4 | Visitor reaches `/dashboard/welcome` (account created) | SourceLoop counts every unique visitor who matches step 1, then checks how many of them also matched step 2, then step 3, then step 4, **in order**. The numbers fall as you go down the steps. That falloff is the **drop-off**, and the percentage who reach the final step is the **conversion rate**. ## What you'll get from a funnel In one screen: - **Conversion rate from step 1 to the final step**, the headline metric - **Per-step drop-off counts and percentages**, the diagnostic detail - **Median time** from step 1 to each subsequent step (how long does each transition take?) - **A visual chart** showing the funnel collapsing from step to step - **A breakdown** by channel, source, country, device, etc., so you can see which segments convert better - **Period-over-period comparison**, did this funnel improve or regress since last quarter? Funnels are the right tool when: - You suspect a specific step in your conversion journey is leaking, but you don't know where - You ran an A/B test on a step and want to measure its impact end-to-end - You're trying to compare conversion rates across channels for the same journey - You need to show stakeholders a clear before/after when you changed a landing page or form ## Step 1: Build a funnel 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Funnels** in the left sidebar. 3. Click **+ New funnel** at the top-right. The funnel builder opens as a side drawer with three numbered sections. ### Section 1: Name the funnel Give it a meaningful name like "Pricing → Signup → Activated" or "Demo Request to Closed-Won". The name is what shows up in the funnel list and at the top of the report, so make it scannable. ### Section 2: Add the steps SourceLoop supports **two types of steps**: | Step type | Matches | Example | |---|---|---| | **Page view** | A specific URL or URL pattern on your site | `/pricing` or `/blog/*` | | **Custom event** | An event name fired by your tracker | `form_submitted`, `signup_completed` | For each step: 1. Pick the **step type** (Page view or Event). 2. Pick the **match operator**: - **Equals** — exact URL match (`/pricing` matches only `/pricing`) - **Starts with** — prefix match (`/blog/` matches every URL beginning with `/blog/`) - **Contains** — substring match (`pricing` matches any URL containing the word "pricing") 3. Type the URL or event name. 4. (Optional) Give the step a friendly name like "Lands on pricing" instead of showing the raw URL on the report. You can add **2 to 10 steps**. Most useful funnels have 3-5; beyond that, drop-off compounds in confusing ways. > **Live match preview** > As you configure each step, SourceLoop fires a live preview showing how many visitors matched that step in the last 30 days. Use this to sanity-check your URL pattern, if a step shows 0 matches, your URL is probably wrong or your tracker isn't firing on that page. ### Section 3: Configure the settings Three settings control how the funnel counts visitors. The defaults work for 90% of funnels, but tweaking them is sometimes necessary. | Setting | Options | Default | When to change | |---|---|---|---| | **Scope** | Per visitor / Per session / Per identified user | Per visitor | Switch to "Per session" if you care about completing the funnel in a single visit. Switch to "Per identified user" to track funnel progression after the visitor identifies themselves (logged-in user behaviour). | | **Step order** | Sequential / Any order | Sequential | Switch to "Any order" when the steps don't have to happen in a specific order. Example: a research-then-buy funnel where the order of pricing-page visits vs feature-page visits doesn't matter. | | **Conversion window** | None / N days | None | Set a window when you want to enforce a time limit, e.g., "completes within 7 days". | Click **Save funnel**. SourceLoop computes the funnel against your historical data immediately, so you'll see results within a few seconds. ## Step 2: Read the Overview report Open the funnel from the list and you'll land on the **Overview** tab by default. Three things on this page: ### The three KPI cards | Card | What it means | |---|---| | **Entered** | Unique visitors who matched the first step in the chosen date range | | **Completed** | Unique visitors who matched the final step (after matching all preceding steps, in order) | | **Conv. rate** | Completed ÷ Entered, expressed as a percentage. The headline number. | If you set a conversion window during build, the Conv. rate card will show it like "Conv. rate (within 7d)". If you left it open, it shows "any time in window". ### The visual chart Below the KPI cards is a column chart. Each column is a step. The height of the column represents the count of visitors who reached that step. As you read left to right, the columns get shorter, that's drop-off. ![SourceLoop Funnel Overview tab showing the Entered, Completed, and Conversion rate KPI cards, the column-style funnel visualisation, and the per-step breakdown table](/help/screenshots/Sourceloop-funnel-overview.webp) The first column is labeled **Entry point** (orange). The final column is labeled **Funnel completion rate** with the percentage shown in green. Intermediate columns show the drop-off count and percentage between them. ### The per-step table Below the chart, a tabular breakdown shows: | Column | What it shows | |---|---| | **Step** | Step name + position | | **Definition** | The condition (URL, event name, operators) | | **Visitors** | Unique count who reached this step | | **% of entered** | Reach count ÷ entry-step count | | **% from previous** | Reach count ÷ previous step's reach count | | **Drop-off %** | 100% − (% from previous) | | **Drop-off count** | How many visitors dropped at this step | The two drop-off columns are the heart of the diagnostic, **the biggest drop-off percentage is your weakest step**. That's almost always where to invest UX or copy improvements first. ## Step 3: Compare two periods The **Compare** tab shows your funnel side by side for two time periods. Use it to answer "Did the changes we made this quarter actually improve conversion?" 1. Click the **Compare** tab at the top of the funnel report. 2. Pick a comparison preset: **Previous day / week / month / quarter / year**, or pick a custom range. 3. SourceLoop renders the same chart and table twice, one for the current period (set in the page-level date picker), one for the comparison period. ![SourceLoop Funnel Compare tab showing the same funnel rendered side by side for two date ranges, with conversion rates, per-step counts, and per-step drop-offs for both periods](/help/screenshots/Sourceloop-funnel-comparision.webp) What to look for: - **Funnel conversion rate up or down** — the headline number. If your conversion rate went from 12% to 18%, congratulations. - **Per-step drop-off shifts** — even if the headline number didn't move, individual steps might have changed dramatically. A step that dropped 40% better might be offset by a step that dropped 40% worse. - **Entry volume changes** — sometimes higher conversion rate just means you attracted higher-quality entry traffic, not that the funnel itself improved. ## Step 4: Break the funnel down by dimension The **Breakdown** tab is the most powerful view for diagnosis. It splits the funnel by a dimension you pick, channel, source, country, device, browser, etc., so you can see how conversion differs across segments. 1. Click the **Breakdown** tab. 2. Pick a dimension from the tab strip: **Channel / Source / Medium / Referrer / Country / City / Device / Browser**. 3. Pick an **attribution model** from the model selector. The default is the workspace default (usually Last Non-Direct, matching Google Analytics). 4. SourceLoop shows a table with one row per dimension value (e.g., one row per channel), each with **Entered**, **Completed**, and **Conv. rate** columns. Useful patterns: - **Channel with low Entered but high Conv. rate** → small but efficient channel; consider investing more there - **Channel with high Entered but low Conv. rate** → high traffic, weak fit; tighten targeting or the landing page - **Mobile rate meaningfully lower than Desktop on the same funnel** → your mobile experience has friction; usually fixable with form simplification - **Conv. rate variation by country** → either targeting issues, or localisation opportunities ### Multi-model attribution in Breakdown The Breakdown tab supports SourceLoop's full set of attribution models. You can stack multiple models in the same view, so a single table can show **Last Touch conversions** alongside **First Touch conversions** alongside **Linear conversions** for the same channels in the same funnel. The gap between Last Touch and First Touch credit for a channel = its assist value within this funnel. Useful for justifying top-of-funnel investment when defending budget. For more on the seven models, see [7 Types of Attribution Models](/help/types-of-attribution-models/). > **Why multi-touch models show fractional visitor counts** > When the attribution model is Linear or Time-Decay, credit splits across all touches in the journey. A visitor whose path was social → email → search before completing the funnel contributes 0.33 of a visitor to each channel under Linear. The fractional counts are mathematically correct — they just look unfamiliar. ## Step 5: Manage funnels The **Funnels list page** shows every funnel for the current website, with summary stats (Entered / Completed / Conv. rate) for the default date range. For each funnel, the three-dot menu offers: | Action | What it does | |---|---| | **Edit** | Reopens the wizard to change steps, name, or settings | | **Archive** | Hides the funnel from the list but preserves it (you can unarchive later) | | **Delete** | Permanent removal, no undo | For funnels you might use again (seasonal campaigns, A/B tests run periodically), use Archive. For mistakes or duplicates, Delete. ## Common funnel patterns A few funnels worth setting up early in your workspace: | Funnel | Steps | |---|---| | **Lead capture funnel** | Lands on `/pricing` → Lands on `/signup` → Submits signup form | | **Demo to closed-won** | Submits demo form → Books meeting → Converts to paid | | **Blog to subscriber** | Lands on `/blog/*` → Submits newsletter form | | **Trial activation** | Signup completed → Lands on `/dashboard/onboarding` → Lands on `/dashboard/(any feature)` | | **Checkout funnel (ecommerce)** | Lands on product page → Adds to cart → Reaches checkout → Payment complete | Once a funnel is set up, it computes against historical data automatically, so you can immediately see the last 30, 60, or 90 days of progression. ## Scope, order, and conversion window in detail These three settings often confuse first-time funnel users. A quick reference: ### Scope | Scope | Counts unique | When to use | |---|---|---| | Per visitor | Unique anonymous IDs (the default) | Almost always; tracks the same person across sessions even before they identify themselves | | Per session | Unique session IDs | When you want to measure a single-visit completion, e.g., "do they finish checkout without leaving?" | | Per identified user | Unique user IDs (after identify call) | When tracking post-signup behaviour (in-product funnels) | ### Step order | Order mode | Behaviour | When to use | |---|---|---| | Sequential | Steps must occur in temporal order (the default) | Standard linear funnels: pricing → signup → activation | | Any order | Steps can occur in any order, but all must occur within the window | Research funnels where order doesn't matter; e.g., visit pricing + visit features + book demo, in any sequence | ### Conversion window | Setting | Behaviour | |---|---| | None (default) | No time limit, the funnel counts completions within the report's date range | | N days | Enforces that the visitor completes all steps within N days of step 1 | A typical pattern: set a 7-day window for trial activation funnels (the visitor needs to activate within the trial period) but leave it unbounded for B2B sales funnels that span months. ## What's next - **Pick the right attribution model for the Breakdown tab:** [7 Types of Attribution Models](/help/types-of-attribution-models/) - **See channel-level traffic data:** [Traffic dashboard](/help/traffic-dashboard/) - **See landing-page-level data:** [Content dashboard](/help/content-dashboard/) - **See the literal sequence of channels converters went through (instead of conditions you defined):** [Paths dashboard](/help/paths-dashboard/) ## Frequently Asked Questions ### How is a funnel different from the Traffic or Content dashboards? The dashboards summarise what happened — visits, conversions, revenue grouped by channel or page. A funnel measures progression, did the same visitor go from step A to step B to step C, in order, and where did they drop out. Use the dashboards to track totals; use funnels to find the specific step where your conversion journey is leaking. ### How many steps can a funnel have? Between 2 and 10. Two is the minimum (you need an entry and a goal). Ten is the maximum SourceLoop supports per funnel. Most useful funnels have 3-5 steps, more than that and drop-off rates become hard to interpret because the cumulative loss compounds. ### What counts as a step? A page view or a custom event. For pageviews, you can match by URL with operators like equals, starts_with, or contains, so /pricing matches just the pricing page, while /blog/* matches every blog post. For custom events, you match by the event name your tracking pixel fires (form submitted, signup completed, etc.). ### Does the funnel respect the global attribution model? On the Breakdown tab, yes. You can pick which attribution model gets applied when breaking the funnel down by channel, source, medium, country, device, or browser, including multi-touch models like Linear, Position-Based, and Time Decay. The Overview tab uses a literal raw count of who reached each step, no attribution model needed there. ### What's the conversion window? An optional time limit between step 1 and the final step. If you set a 7-day window, only visitors who completed all steps within 7 days of their first step are counted as converted. If you leave it empty, there's no time limit, the funnel just looks across whatever date range you've picked at the top. ### Can I share a funnel with my team? Funnels are scoped to your website (or workspace), so every teammate with access to the website automatically sees and can edit the same funnels. There's no separate "share link" — anyone on the team can open the funnel by name from the funnels list. ### What's the difference between archiving and deleting a funnel? Archive hides the funnel from the list but preserves its definition and historical data. You can unarchive later. Delete is permanent, the funnel is removed and can't be recovered. For funnels you might use again (seasonal campaigns, A/B test reporting), archive. For mistakes or duplicates, delete. ### Why are the Linear or Time-Decay credits showing fractional visitor counts in the Breakdown tab? Because multi-touch attribution models split credit across all touches in the journey. If a visitor's path was social → email → search before completing the funnel, Linear gives each channel 0.33 of a visitor instead of 1.0 to the last channel. The fractional counts are real and add up correctly; they just look unusual at first glance. --- # How to Create a Conversion Funnel Analysis Build a conversion funnel in SourceLoop, covering naming, step types, scope and order, and saving, with live match counts at every step. Source: https://sourceloop.ai/help/how-to-create-a-conversion-funnel/ Updated: 2026-05-28 --- This guide walks through the exact mechanics of creating a funnel in SourceLoop. It assumes you already know what a funnel is and what you'll use the results for, if not, start with [How to Build and Analyse Conversion Funnels](/help/conversion-funnels-guide/). ## Before you start You'll need: - A **SourceLoop workspace** with the [tracking pixel installed](/help/install-the-tracking-pixel/) - At least **30 days of historical traffic** so the live preview returns meaningful counts (or however much data you have, it just won't be much) - A clear idea of the **conversion sequence** you want to measure, sketch it on paper first if you're unsure of the steps - **Admin** or **Owner** role in SourceLoop (Editors can create funnels, but only Admins can manage workspace-wide views) ## Step 1: Open the funnel builder 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Click **Funnels** in the left sidebar. 3. Click **+ Create Funnel** at the top-right of the funnel list. ![SourceLoop Funnels list page with the Funnels sidebar entry and the Create Funnel button highlighted](/help/screenshots/sourceloop-funnel-page.webp) The Create funnel drawer opens on the right with three numbered sections. ## Step 2: Name the funnel and add steps ### Naming Give the funnel a name that tells you the journey at a glance. Good names: - **"Pricing → Signup → Activated"** - **"Home → Pricing → Demo Booked"** - **"Blog → Newsletter Signup"** Bad names: "Funnel 1", "Test", "My funnel" (you'll regret these in two weeks). The placeholder text in the field suggests `e.g. Visitor → signed up → purchased` — that's the right shape. ### Steps ![SourceLoop Create funnel drawer showing Step 1 and Step 2 with Page view selected, equals operator, the URL field, and the live match preview underneath each step](/help/screenshots/sourceloop-step-1-and-2.webp) Click **+ Add step** to add each step. For each one, decide: **Step type** (top of the step card): | Type | What it matches | |---|---| | **Page view** | A specific URL on your site being loaded | | **Event** | A custom event your tracker fires (e.g., `form_submitted`) | For Page view steps, pick a **match operator**: | Operator | What it matches | Use when | |---|---|---| | **equals** | Exact URL match | You want only one specific page (e.g., `/pricing` and nothing else) | | **starts with** | URL prefix match | You want a section of your site (e.g., `/blog/` covers every blog post) | | **contains** | Substring match | You want any URL containing a keyword (rarely needed; only use when equals and starts_with don't fit) | Type the URL path (e.g., `/pricing`) or event name (e.g., `signup_completed`). **Live match counter**: SourceLoop fires a live query ~350ms after you finish typing and shows "X matches in last 30 days" below the field. Use this to confirm your pattern is right. > **Sanity-check every step with the live counter** > If a step shows 0 matches and you know the page or event gets traffic, your URL string or operator is wrong. Maybe you typed `/pricing/` (trailing slash) instead of `/pricing`. Maybe the event name has a capital letter. Fix it now, before saving. **Step naming (optional)**: Each step shows as "Step 1", "Step 2", etc. by default. The funnel report uses these labels on the chart and table. If you'd rather see "Lands on pricing" instead of "Step 1, /pricing equals", rename the step from the step card. **Add more steps**: click **+ Add step** at the bottom of the Steps section. Minimum 2 steps, maximum 10. ## Step 3: Configure settings and save ![SourceLoop Create funnel drawer with the Settings section visible, showing Count unique (Per visitor anonymous ID), Step order (Sequential in order), and the Advanced options link](/help/screenshots/Sourceloop-advanced-settings-funnel.webp) Section 3 in the drawer has the funnel's behaviour settings. ### Count unique What gets counted as "one" visitor in the funnel: | Option | What it means | Use when | |---|---|---| | **Per visitor (anonymous ID)** | Same anonymous browser identifier across sessions (the default) | Almost always; tracks the same person across visits even before they identify themselves | | **Per session** | A single visit (new session after 30 minutes of inactivity or new referrer) | You want to measure single-visit completion ("did they finish checkout in one go?") | | **Per identified user** | A logged-in user identity | You're tracking in-product behaviour after signup | ### Step order How strict the order requirement is: | Option | Behaviour | Use when | |---|---|---| | **Sequential (in order)** | Steps must occur in temporal order (the default) | Standard linear funnels: pricing → signup → activation | | **Any order** | Steps can happen in any sequence as long as all occur within the window | Research funnels where the order doesn't matter (e.g., visit pricing + visit features + book demo, in any sequence) | ### Advanced options Click **Advanced options** to reveal the conversion window: | Setting | Behaviour | |---|---| | **No window** (default) | Steps can be completed any time within the report's date range | | **N days** | All steps must be completed within N days of step 1 | A typical pattern: set a 7-day window for trial-activation funnels (the visitor needs to activate during the trial), leave it open for B2B sales funnels that span months. ### Description (optional) Click **▶ Add description** at the bottom to add a free-text note. Useful for documenting "why this funnel exists", especially for funnels shared across the team. ### Save Click **Create funnel** in the bottom-right of the drawer. SourceLoop computes the funnel against your historical data immediately. You'll land on the funnel's Overview report within a few seconds. From there, see [How to Build and Analyse Conversion Funnels](/help/conversion-funnels-guide/) for reading the report. ## A worked example To make the wizard concrete, here's a complete lead-capture funnel filled in: | Field | Value | |---|---| | Funnel name | `Pricing → Signup → Activated` | | Step 1 | Page view · equals · `/pricing` | | Step 2 | Page view · equals · `/signup` | | Step 3 | Event · `signup_completed` | | Step 4 | Page view · starts with · `/dashboard/` | | Count unique | Per visitor (anonymous ID) | | Step order | Sequential (in order) | | Conversion window | (none — open) | This funnel measures: of all visitors who hit `/pricing`, how many later visited `/signup`, then completed the signup, then made it to the dashboard. The Overview report will show drop-off percentages between each pair of steps. ## What's next - **Read the funnel report once it's built:** [How to Build and Analyse Conversion Funnels](/help/conversion-funnels-guide/) - **Understand what's happening under the hood when SourceLoop counts visitors through the funnel:** [How SourceLoop Computes Funnels](/help/how-sourceloop-computes-funnels/) - **Pick an attribution model for the Breakdown tab:** [7 Types of Attribution Models](/help/types-of-attribution-models/) ## Frequently Asked Questions ### How many steps does a funnel need? At least 2, no more than 10. A 2-step funnel is just a conversion-rate measurement (e.g., "% of pricing-page visitors who reach checkout"). 3-5 steps is the sweet spot for diagnostic funnels. More than 5 starts compounding drop-off in ways that get hard to interpret. ### What's the difference between a Page view step and an Event step? A Page view step matches when a visitor loads a URL on your site (e.g., `/pricing`). An Event step matches a custom event your tracking pixel fires (e.g., `form_submitted` or `signup_completed`). Most funnels mix both, page views capture organic browsing, events capture actions like form submits. ### Can I edit a funnel after I've created it? Yes. Click the three-dot menu on the funnel from the list and pick Edit. The wizard reopens with all your previous settings filled in. Changes recompute against historical data immediately, so you'll see updated counts within a few seconds of saving. ### What happens if my step URL pattern is wrong? The live preview shows "0 matches in last 30 days" or a suspiciously low number. That's your sanity check, if a step shows no matches but you know the page gets traffic, the operator (equals vs starts_with) or the URL string is wrong. ### Should I use 'equals' or 'starts with' for page-view steps? Use 'equals' when there's only one URL that should match the step (e.g., `/pricing` exactly). Use 'starts with' when multiple URLs share a prefix and you want to count all of them (e.g., `/blog/` matches every blog post). Use 'contains' as a last resort when the matching string can appear anywhere in the URL. ### What does the 'matches in last 30 days' counter actually measure? It's a real, live preview of how many unique visitors matched this specific step's condition in the last 30 days, scoped to your current website. It updates ~350ms after you finish typing. If you're wondering whether your URL pattern is right, this is the fastest way to tell. --- # How SourceLoop Computes Funnels How SourceLoop turns your funnel definition into Entered, Completed, and per-step drop-off numbers, with how scope, order, and window affect the math. Source: https://sourceloop.ai/help/how-sourceloop-computes-funnels/ Updated: 2026-05-28 --- This article explains how SourceLoop actually turns a funnel definition into the numbers you see on the report. It's not strictly required reading, you can use funnels successfully without it. But if you've ever asked "why is the conversion rate different from what I expected?" or "why does this number change when I switch scopes?", this is the answer. ## The three things a funnel needs to compute To produce a funnel report, SourceLoop needs to answer three questions: 1. **Who counts as a single "visitor"** through the funnel? (the **scope**) 2. **What order do the steps need to happen in**, if any? (the **order mode**) 3. **Is there a time limit** between step 1 and the final step? (the **conversion window**) Your funnel definition sets all three. SourceLoop then runs the actual matching against your stored events. ## How matching actually works For every visitor (or session, or identified user, depending on your scope) within the report's date range, SourceLoop checks: ``` Did this visitor's event history match step 1's condition? → If no, skip them entirely. They don't appear in Entered. Did the same visitor match step 2's condition? → If sequential mode, after step 1. If any-order, anywhere in the window. Did the same visitor match step 3's condition? → Same logic. ... and so on for every step in the funnel. ``` Visitors who matched **only step 1** are in Entered but not Completed. Visitors who matched **every step** are in both. The drop-off at each step is the count of people who got that far but no further. ## How scope changes the math **Scope** decides what counts as "one" through the funnel. | Scope | The identity used | Example: same person visits 5 times | |---|---|---| | **Per visitor** (default) | Anonymous browser ID (cookie-based) | Counts as **1** visitor | | **Per session** | Session ID (new after 30 min idle or new referrer) | Counts as **5** sessions | | **Per identified user** | User ID (after the visitor identifies, e.g., signs up) | Counts as **1** identified user (or 0 if they never identified) | The same funnel produces different numbers depending on which one you pick. Per visitor is right for most lead-funnel analysis. Per session is right for single-visit completion (checkout funnels). Per identified user is right for in-product behavioural funnels. ## How order mode changes the math **Order mode** decides whether the steps have to happen in a specific sequence. ### Sequential (the default) A visitor only counts as having reached step N if they previously reached steps 1 through N-1, **in temporal order**. Visitor's actual event history: A → B → C → D (in chronological order) Funnel definition: step 1 = B, step 2 = D Does the visitor count? **Yes.** They reached B (step 1) and later reached D (step 2), in order. Events A and C are noise; the funnel ignores them. If the funnel definition were step 1 = D, step 2 = B, the visitor would **not** count, because B happened before D, not after. ### Any order The order requirement is dropped. The visitor just needs to have matched every step's condition at some point, in any order, within the window. Same visitor history: A → B → C → D Funnel definition: step 1 = D, step 2 = B (any order mode) Does the visitor count? **Yes.** They matched both D and B; the temporal order doesn't matter. Any-order mode is useful when you're measuring research patterns (e.g., did they view pricing AND features AND book a demo, in any order?) but rarely useful for true sales funnels (where order genuinely matters). ## How the conversion window changes the math The **conversion window** is an optional time limit between step 1 and the final step. | Window | Visitor's history | Counts as completed? | |---|---|---| | No window | Step 1: Day 1. Final step: Day 60. | **Yes** (within the report's date range) | | 7 days | Step 1: Day 1. Final step: Day 60. | **No** (60 days > 7-day window) | | 7 days | Step 1: Day 1. Final step: Day 5. | **Yes** (within the window) | Use a window when the journey **must** complete within a known time (a trial period, a campaign, a sale duration). Skip the window when you genuinely don't care how long the conversion took. ## How attribution model applies (only on Breakdown) The **Overview** and **Compare** tabs use raw step-reach counts. There's no attribution model involved, because there's no credit-allocation decision being made; you're just counting people who reached each step. The **Breakdown** tab is different. When you break the funnel down by channel (or source, or country, etc.), SourceLoop has to decide **which channel to credit** for each completion. That's where the attribution model comes in. | Model | What it does in the Breakdown | |---|---| | **Last Touch** | Credits the last channel the visitor came from before completing the final step | | **First Touch** | Credits the first channel that brought the visitor into your site | | **Last Non-Direct** | Like Last Touch, but skips Direct sessions if they're the last touch | | **First Non-Direct** | Like First Touch, but skips Direct sessions if they're the first | | **Linear** | Splits credit equally across every channel in the journey | | **Position-Based (U-Shaped)** | 40% first, 40% last, 20% middle | | **Time Decay** | Weights recent touches more heavily | Single-touch models (Last Touch, First Touch, etc.) produce whole visitor counts. Multi-touch models (Linear, Position-Based, Time Decay) produce **fractional** counts because credit is split. So the Breakdown table can show "0.33 visitors" for a channel under Linear, which is mathematically correct: a visitor with three touches contributed one-third to each of the three channels. For deeper detail on each model, see [7 Types of Attribution Models](/help/types-of-attribution-models/). ## Why two funnels with the same steps can show different numbers A common confusion: you have two funnels with identical step definitions, but they show different Entered or Completed counts. Five things can cause this: | Difference | What you'll see | |---|---| | Different **date ranges** | Different Entered (most common) | | Different **scope** | Per visitor vs Per session vs Per identified user produce different counts | | Different **order mode** | "Any order" usually shows higher counts than "Sequential" | | Different **conversion window** | Tighter windows produce lower Completed | | One funnel has **archive flag** on the underlying data | Rare; happens if you've manually excluded sessions | When you're debugging unexpected numbers, walk through this checklist before assuming a bug. ## What freshness to expect Funnel computations read from the same event store that powers every other SourceLoop dashboard. That means: - **Page views** appear in funnel computations within seconds of loading the page - **Custom events** appear within seconds of the tracker firing them - **Conversions** (forms, meetings, chats, payments) appear within seconds (forms / chats / meetings via the tracker) or within minutes (payments via webhook from the payment processor) There's no precomputation, no overnight batch job, no cache to flush. Every time you open a funnel report or change a filter, SourceLoop runs the matching live against your latest data. The trade-off is that funnel reports take a few seconds to load (versus instantly), but the data is always current. For most teams that's the right trade. ## What's next - **Build your first funnel:** [How to Create a Conversion Funnel](/help/how-to-create-a-conversion-funnel/) - **Read the funnel report once it's built:** [How to Build and Analyse Conversion Funnels](/help/conversion-funnels-guide/) - **Pick the right attribution model for the Breakdown tab:** [7 Types of Attribution Models](/help/types-of-attribution-models/) ## Frequently Asked Questions ### Are funnels precomputed or live? Live. Every funnel computation happens on-demand when you open the report or change a filter. There's no cached or aggregated result, the chart, the table, and the breakdown all read fresh data each time. That's why changing a date range, attribution model, or dimension recomputes the whole view in a few seconds. ### How fast is the data? New conversions and pageviews appear in funnel computations within seconds. There's no batch job or overnight refresh; the funnel reads from the same fresh event store that powers the Traffic, Content, and Paths dashboards. ### Why does the same funnel show different numbers when I change Count unique? Because the underlying "thing" being counted changes. Per visitor counts unique anonymous browsers. Per session counts unique visits. Per identified user counts unique logged-in identities. A single person who visits five times shows as 1 under Per visitor, 5 under Per session, and 1 under Per identified user (if they're logged in). ### Why does the Breakdown tab show fractional visitor counts? Multi-touch attribution models like Linear, Position-Based, and Time Decay split credit across all touches in the journey. A visitor with a 3-touch path produces 0.33 of a visitor in each channel under Linear, not 1.0 in the last channel. The fractional counts are mathematically correct and add up to the real visitor count when summed across channels. ### What's the difference between 'Funnel completion' and 'Conversion rate'? Funnel completion is the count of unique visitors who reached the final step. Conversion rate is that count divided by the count who entered (reached step 1), expressed as a percentage. Completion is a raw number; Conversion rate is a ratio. The report shows both. ### If a visitor reaches step 1 today and step 2 tomorrow, do they count if my date range is 'today'? The date range filters by when step 1 was reached, not when the final step was reached. So if step 1 happened today and step 2 happened tomorrow, the visitor counts as having entered today; whether they show as completed depends on whether the report extends to tomorrow. This is consistent with how Entered is typically defined for funnel reports. ### Do funnels respect the global attribution model on the Overview tab? No. Overview uses raw step-reach counts; an attribution model isn't needed because there's no "credit to assign" decision at the Overview level. The model applies only on the Breakdown tab, which is where credit gets split across channels or other dimensions. --- # How to Use the SourceLoop Contacts (Leads) Table The Contacts table, one row per identified lead with first-touch and last-touch attribution, lifecycle status, revenue, and the full visitor journey. Source: https://sourceloop.ai/help/contacts-leads-table/ Updated: 2026-05-29 --- The **Contacts table** is the row-per-lead view in SourceLoop. Every identified contact, anyone who left an email, phone number, or other handle through a form, meeting, chat, or payment, gets one row. The table shows who they are, what brought them in, what they're worth, and how to drill into their full journey. It lives at **Contacts** in the left sidebar. The same page also has a [Visitors view](/help/contacts-anonymous-visitors/) for anonymous browsers who never converted. ## What the Contacts table answers In one screen: - **Who has converted recently**, with full contact details - **What channel, source, campaign, and landing page brought each lead in** (both first and last touch) - **Where each lead is in your sales lifecycle** (Status + Qualified flag) - **What each lead is worth** (Expected Revenue, Revenue, Lead Score) - **What the full journey looked like** before they converted If the [Traffic dashboard](/help/traffic-dashboard/) is "what channels are working", the Contacts table is "show me the actual people each channel produced". ![SourceLoop Contacts table with one row per lead, showing contact details, conversion type, lifecycle status, revenue, and first and last touch attribution columns](/help/screenshots/SourceLoop-leads-table.webp) ## Step 1: Pick your date range and filters The page-level controls at the top: 1. **Date range** — defaults to the last 30 days 2. **Saved view** (left sidebar) — load a previously saved combination of filters, sort, and visible columns 3. **Column filters** (under each header) — quick text or dropdown filter per column 4. **Advanced filter** — build multi-condition filters across any columns (e.g., "Channel = Paid Search AND Country = US AND Revenue > 0") The filter bar is sticky. Add filters and the table re-queries in real time. ## Step 2: Read the row Each row is one lead. The most useful default columns: | Column | What it shows | |---|---| | **Contact** | Email or phone, with an avatar initial. The pinned identity column. | | **Name** | Lead's name if the conversion source included one. Blank if not. | | **Date** | When the lead converted. Click to edit. | | **Type** | What kind of conversion: Web Form, Meeting, Chat, Payment, Subscription, API, CRM Import, or Custom. | | **Status** | Lifecycle stage (New, Contacted, In Progress, Converted, Lost). Configurable per workspace. | | **Qualified** | Yes / No / Not Set flag for sales qualification. | | **Lead Score** | Numeric score your team or workflow assigns. | | **Expected Revenue** | The forecasted deal value (quote). | | **Revenue** | The realised deal value (closed-won amount). | All of these are click-to-edit inline; no separate edit mode. ## Step 3: Read the attribution columns Two parallel sets of attribution columns, one for the lead's **first** touch (how they originally found you) and one for the **last** touch (the session in which they converted): | First touch | Last touch | Meaning | |---|---|---| | First Channel | Latest Channel | High-level bucket (Paid Search, Organic Social, Direct, etc.) | | First Source | Latest Source | The `utm_source` value (google, linkedin, twitter, etc.) | | First Medium | Latest Medium | The `utm_medium` value (cpc, organic, social, email) | | First Campaign | Latest Campaign | The `utm_campaign` value | | First Keyword | Latest Keyword | Search term that landed them on the page | | First Content | Latest Content | The `utm_content` value | | First Landing Page | Latest Landing Page | URL they first arrived on / converted on | | First Landing Page Folder | Latest Landing Page Folder | The URL folder of either | Plus, both first and last touch carry the **click IDs** for each ad platform: Google (gclid, gbraid, wbraid), Microsoft (msclkid), Facebook (fbclid), and LinkedIn (li_fat_id). Those are useful for debugging mismatches between SourceLoop attribution and the ad platforms' own reporting. ## Step 4: Read the contact and technical columns | Column | What it shows | |---|---| | **Company** | Company name if captured (calendar bookings often include this) | | **Phone** | Phone number if captured | | **Country** | Country derived from the converting session's IP | | **City** | City derived from the same | | **Browser** | Browser used at the converting session | | **Device Type** | Desktop, Mobile, or Tablet | | **OS** | Operating system | And two flags: - **Is Spam** — manual toggle for cleaning up junk submissions - **Is Duplicate** — manual toggle when the same person appears twice Plus **Notes** (free text), **First Seen** (read-only), and **Last Seen** (read-only). ## Step 5: Show / hide columns and save the view That's a lot of columns. Most teams don't need all of them visible at once. 1. Click the **Column visibility** menu (right of the table header). 2. Toggle columns on or off. The list has a search box; type "click" to surface just the click ID columns, etc. 3. Click **Save view** to keep this column layout (plus current filters and sort) as a named view. It appears in the left sidebar for one-click recall. Useful preset views to create: - **Sales pipeline** — visible: Contact, Status, Qualified, Expected Revenue, Latest Channel; sorted by Date desc. - **Paid Search performance** — filter Latest Channel = Paid Search; visible: Contact, Latest Campaign, Latest Keyword, Revenue. - **High-value leads** — filter Lead Score > 50; visible: Contact, Status, Expected Revenue, Latest Channel. ## Step 6: Open the lead drawer for the full journey Click any row. The right-side **lead drawer** opens with the lead's full pre-conversion journey. ![SourceLoop lead detail drawer with header showing name and email, an editable status and revenue panel, attribution sections for first and last touch, and a vertical journey timeline of every page view, ad click, and form submission leading to the conversion](/help/screenshots/sourceloop-lead-journey-demo.webp) The drawer has two sides: **Left side**, expandable accordions for the lead's metadata: - **Last Touch Attribution** — channel, source, medium, campaign, keyword, content, landing page (clickable to open in a new tab) - **First Touch Attribution** — same fields, for the original touch - **Personal Information** — email, name, company, phone, country, city (all inline editable) - **Technical Information** — browser, device, OS - **Additional Information** — date, mark as spam, mark as duplicate **Right side**, a vertical timeline of every event leading to the conversion: - The **conversion event** at the top (form submission, meeting booking, payment, etc.) with every field captured - Every **session** the lead had on your site, expanded into the individual events: page views, ad clicks, custom events, form interactions, video plays, scroll milestones, etc. - Each event timestamped, with the page URL or event metadata inline Click any event to expand details below. The timeline is the single most useful artifact in SourceLoop for understanding "why did this lead convert from this campaign?" ## Step 7: Export to CSV Top-right of the table, **Export**. The CSV download includes every visible column for every row matching the current filters, with attribution flattened into row-per-lead format. Typical exports: - **Sales handoff** — filter to recent qualified leads, export with contact details and Latest Channel / Campaign for sales follow-up - **ROAS analysis** — filter to a date range, export with Revenue, First Channel, and Latest Channel; analyse in your spreadsheet of choice - **CRM backfill** — export historical leads with their attribution for backfilling a new CRM ## What's next - **Browse anonymous visitors who haven't converted yet:** [Anonymous visitors view](/help/contacts-anonymous-visitors/) - **Compare how attribution credit shifts across models:** [7 Types of Attribution Models](/help/types-of-attribution-models/) - **Slice the same data by channel rather than per-lead:** [Traffic dashboard](/help/traffic-dashboard/) - **See pre-conversion page patterns in aggregate:** [Paths dashboard](/help/paths-dashboard/) ## Frequently Asked Questions ### What's the difference between the Leads table and the Visitors table? The Leads table shows identified contacts, anyone who left an email, phone number, or other identifying detail through a form, meeting booking, chat, or payment. The Visitors table shows anonymous browsers who came to the site but never converted. Same underlying journey data, just split by whether SourceLoop has a contact handle yet. ### How do I save a filter I use often? Set your filters, sort, and visible columns the way you want them, then click the Save view button. Give it a name (e.g., "Hot leads this week" or "Paid Search demos"). Saved views appear in the left sidebar, one click to load them later. Each user has their own views. ### Can I export the table to CSV? Yes. Click the Export button top-right. The CSV includes every visible column for every row that matches your current filters, with attribution and journey data flattened into row-per-lead format. Useful for handing off to sales ops, importing into spreadsheets, or backfilling another tool. ### Can I edit a lead's status, score, or revenue inline? Yes. Click any editable cell (Status, Qualified, Lead Score, Expected Revenue, Revenue, Notes, Is Spam, Is Duplicate) and edit in place. The change saves automatically and propagates to any CRM you've connected within minutes. ### What does the Qualified column mean? A simple Yes / No / Not Set flag your team can use to mark whether a lead is sales-qualified. It's not auto-calculated, you set it manually (or via API). The Status column is the longer lifecycle stage (New, Contacted, In Progress, Converted, Lost); Qualified is the binary "should sales chase this?" flag. ### Why are some lead names blank? Because the conversion source didn't include a name field. Calendar bookings always include a name; web forms only do if you have a Name field on the form. The Contact column always falls back to the email or phone number so you can still identify the lead. ### How do I open a lead's full journey? Click any row in the table. The right-side drawer opens with the lead's entire pre-conversion journey, every page they viewed, every ad they clicked, every form attempt, every session, in reverse chronological order. The drawer is where most of the attribution story lives. --- # How to Use the SourceLoop Anonymous Visitors View The Visitors view, every anonymous browser that came to your site. See engagement, location, device, attribution, and the full pre-conversion journey. Source: https://sourceloop.ai/help/contacts-anonymous-visitors/ Updated: 2026-05-29 --- The **Visitors view** is the other half of SourceLoop's Contacts page, everyone who has come to your site but hasn't converted yet. No email, no name, no phone number, just an anonymous browser, its sessions, and the attribution attached to those sessions. It's the right view for diagnosing top-of-funnel performance: are paid clicks coming in but not converting, is one campaign driving unusually engaged anonymous visitors, are organic visitors browsing deeply but bouncing without filling a form. Open it from the left sidebar of the Contacts page, **Visitors -> All visitors**. ## What the Visitors view answers In one screen: - **How many anonymous visitors arrived in a date range**, and from which channels - **How deeply they engaged** (sessions, pageviews, an engagement bucket) - **Where they came from geographically**, on what device, in what browser - **Their first-touch and last-touch attribution**, even though they haven't converted - **Their full per-visitor journey**, every page they viewed, every ad they clicked ![SourceLoop Anonymous Visitors view with one row per anonymous browser, showing the engagement bar, first seen and last seen timestamps, country, attribution columns, device, and pageview count](/help/screenshots/sourceloop-visitor-journey.webp) ## Step 1: Pick your date range and filters Same controls as the Leads view: 1. **Date range** — defaults to the last 30 days 2. **Column header filter** — quick filter per column (dropdown for Channel / Device / Country; text input for everything else) 3. **Filter builder** — multi-condition filter across any dimension 4. **Column visibility menu** — toggle which columns appear The page-level filters at the top apply across both Leads and Visitors views, so swapping between them keeps your filter context. ## Step 2: Read the row Each row is one anonymous visitor (one unique browser, cookie-based). The default columns: | Column | What it shows | |---|---| | **Visitor** | Either "Anonymous" with an incognito-style avatar, or the name / email if SourceLoop has captured any identifying detail short of a full conversion. | | **Engagement** | Highly / Moderately / Slightly active, with a visual bar. Derived from session count + pageview count. | | **First seen** | When the visitor first arrived (relative, e.g., "2 days ago"). | | **Last seen** | When their most recent session ended. | | **Country** | Country with flag emoji (from the converting session's IP). | | **City** | City from the same. | | **Pageviews** | Total page loads across all sessions. | ## Step 3: Read the attribution columns Same first-touch / last-touch column pairs as the Leads table: | First touch | Last touch | Meaning | |---|---|---| | First Channel | Latest Channel | Paid Search, Organic Social, Direct, etc. | | First Source | Latest Source | The `utm_source` value | | First Medium | Latest Medium | The `utm_medium` value | | First Campaign | Latest Campaign | The `utm_campaign` value | | First Term | Latest Term | Search term | | First Content | Latest Content | The `utm_content` value (hidden by default) | | First Landing Page | Latest Landing Page | URL of the first arrival and the most recent arrival | For a visitor who has only had one session, First and Latest match. For repeat visitors, the two diverge, useful for spotting "first arrived via Paid Search, came back via Direct" patterns. ## Step 4: Read the technical columns | Column | What it shows | |---|---| | **Device** | Desktop, Mobile, or Tablet | | **OS** | Operating system (hidden by default) | | **Browser** | Browser name (hidden by default) | Toggle the hidden columns via the column visibility menu when you need them. ## What you won't see here (compared to the Leads view) The Visitors view is intentionally stripped of every identified-contact field: - No **name**, **email**, **phone**, or **company** - No **status**, **qualified flag**, **lead score**, **expected revenue**, or **revenue** - No **conversion type** (because there's no conversion yet) - No **click IDs in the table** (still tracked underneath, visible in the journey drawer) - No inline editing — the view is read-only The moment a visitor identifies themselves (submits a form, books a meeting, starts a chat with an email, pays), they're promoted to the Leads view and disappear from here. ## Step 5: Open the journey drawer Click any row. The right-side drawer opens, the same drawer the Leads table uses, in read-only mode. For an anonymous visitor, the drawer shows: - **Last Touch Attribution** and **First Touch Attribution** sections, exactly as for an identified lead - **Personal Information** — mostly empty for an anonymous visitor; populated as soon as identifying data lands - **Technical Information** — browser, device, OS - **Journey timeline** on the right side, every session the visitor has had, expanded into individual events: page views, ad clicks, custom events, scroll milestones, video plays, form starts (without submissions), etc. The journey timeline is what makes this view valuable. You can see the pages a Paid Search visitor browsed before bouncing, the ads a return visitor clicked, the videos a high-engagement anonymous visitor watched, all of it, even without a conversion. ## Step 6: Spot the patterns that matter A few useful reading patterns: **High engagement, no conversion** → visitors are getting interested but the path to convert isn't clear. Look at their last viewed page and check the CTA placement and copy. Click their row to see the journey timeline; did they look at the pricing page and bounce? **Specific campaign driving lots of unconverted visitors** → filter Latest Campaign = "your campaign name". If the volume is high but no one converts, the campaign is targeting the wrong audience, the landing page is wrong, or the CTA is too far below the fold. **Return-visitor traffic from Direct after Paid Search** → indicates real interest. These are the visitors most likely to convert next; consider a retargeting campaign that catches them on their third visit. **High organic traffic from a specific country with zero conversions** → either you're not localised for that market, or your form / payment flow is failing for that geo (currency, language, payment method). Drill into a journey to confirm. ## Step 7: Export to CSV Same Export button, top-right. The CSV includes every visible column plus full attribution per visitor, one row per browser. Typical exports: - **Audience upload to ad platforms** — export, hash, and upload as a custom audience to retarget high-engagement non-converters - **Top-of-funnel quality check** — export by Latest Channel to compare engagement metrics across paid vs organic sources - **Bounced-campaign analysis** — filter to a campaign with high cost but no Leads, export, and review session-by-session in a spreadsheet ## What's next - **See identified leads with full revenue and lifecycle data:** [Contacts (Leads) table](/help/contacts-leads-table/) - **Slice the same data by channel rather than per-visitor:** [Traffic dashboard](/help/traffic-dashboard/) - **See pre-conversion page sequences in aggregate:** [Paths dashboard](/help/paths-dashboard/) - **Compare attribution credit across models:** [7 Types of Attribution Models](/help/types-of-attribution-models/) ## Frequently Asked Questions ### Why doesn't this view show names or emails? Because these visitors haven't given you any. The moment a visitor identifies themselves by submitting a form, booking a meeting, starting a chat with an email, or paying, they graduate to the Leads view and stop appearing here. The Visitors view is intentionally the pre-identification stage. ### What counts as a 'visitor'? A unique anonymous browser, identified by a first-party cookie that SourceLoop's tracker sets on first page load. The same browser returning multiple times is one visitor with multiple sessions; a different browser (e.g., switching from phone to laptop) shows as a separate visitor until you can later stitch them via an identifier. ### How is Engagement calculated? A simple synthetic score based on session count and pageview count. Highly active visitors have had multiple sessions or browsed deeply in one. Moderately active have a typical 2-3 page session. Slightly active have a single short session. It's a rough sort, not a model, useful for finding the visitors most worth a follow-up if they later convert. ### Can I export the Visitors view? Yes, the same way as the Leads view. The CSV includes every visible column plus first-touch and last-touch attribution, with one row per anonymous visitor. Useful for ad-platform audience uploads (after hashing) or for analysis when you suspect a campaign is driving large unconverted volume. ### Why are some attribution columns blank? Because the visitor came in without UTM parameters and without a recognised referrer (Direct traffic). The First Channel column will show "Direct" and most other attribution columns will be empty. This is normal for brand-search, direct-typed, and dark-social traffic. ### Can I click a visitor to see their journey? Yes. Click any row to open the journey drawer, the same drawer the Leads table uses, in read-only mode. You'll see every session, every page view, every ad click, every event the visitor has triggered. It's the most useful artifact for diagnosing "why are paid clicks coming in but not converting". ### How long do visitors stay in this view? Until they convert (which moves them to the Leads view) or until their session data ages out of your retention window. Practically, you're looking at every active and recent anonymous visitor at any moment. --- # Section: Integrations Connect SourceLoop to your ad platforms, CRM, and analytics stack through native integrations, incoming and outgoing webhooks, Zapier, and Make. # How to use SourceLoop Incoming Webhooks Send leads into SourceLoop from any tool that fires a webhook. One URL per conversion type, auto-detecting email and phone from any payload. Source: https://sourceloop.ai/help/incoming-webhooks-overview/ Updated: 2026-05-29 --- SourceLoop's **Incoming Webhooks** are how you send leads into SourceLoop from a tool that doesn't have a native integration. Any form builder, scheduling tool, chat tool, or custom app that can POST JSON to a URL when a submission happens can pipe its data straight into your Contacts Hub. This is the path the [semi-automated web form integrations](/help/track-web-form-submissions/) use under the hood. It's also what powers the Zendesk Chat capture flow, and it's the recommended path for anything custom you've built that produces leads. ## When to use an Incoming Webhook You want an Incoming Webhook when: - The form / meeting / chat tool is hosted on a domain that isn't yours, so the SourceLoop tracker can't read the submit event from the browser - You've built a custom lead-capture flow (a backend API, an internal tool, a Slack command that creates a lead) and want it to land in SourceLoop - An existing tool has a webhook action but no native SourceLoop integration You **don't** need an Incoming Webhook when: - The form is embedded on a page that loads the SourceLoop tracker (automated capture handles it) - You're using one of the native CRM, payment, or ad-platform integrations from Setup (those use their own dedicated paths) ## What SourceLoop captures from each webhook delivery Every incoming webhook produces one lead in your Contacts Hub with: - **Email** and / or **phone** auto-detected from the payload - **Name**, **company**, and any other custom fields you send (stored on the lead's custom-data section) - **Conversion type** matching the endpoint (`Web form`, `Meeting`, or `Chat`) - **First-touch and last-touch marketing attribution**, stitched from the visitor's pre-submit journey if the tracker is also installed - **Receive timestamp** and the raw payload for forensic debugging ## Before you start You'll need: - A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up)) - **Admin** or **Owner** role in SourceLoop - A tool that can POST JSON to a URL when a submission happens - (Recommended) The [SourceLoop tracking pixel](/help/install-the-tracking-pixel/) installed on your site if visitors browse your site before the submission ## Step 1: Generate the webhook URL SourceLoop creates one webhook URL per **conversion type** so leads land in the right bucket in reports. 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Incoming Webhooks** in the left sidebar. 3. Pick the conversion type that matches your tool: - **Web form** for form builders, lead-capture tools, surveys - **Meeting** for scheduling tools that don't have a native integration - **Chat** for chat tools that fire webhooks (Zendesk Messaging, custom bots, etc.) 4. Click **Create** on the matching card if no URL exists yet, or **Copy** to grab the URL. The URL contains a secret token unique to your endpoint. Treat it like a private API key. > **Pick the right conversion type** > Reports group leads by conversion type. A scheduling tool sending into the Web Form endpoint will still capture the lead, but the Contacts Hub will mark it as a form submission rather than a meeting, which makes the Traffic dashboard's "Meetings" filter incomplete. ## Step 2: Wire the URL into your tool The exact steps depend on your tool. Most tools call this feature "Webhooks", "HTTP requests", "Outgoing connections", or "Custom integrations". The common shape: 1. Open the integrations / webhooks section in your tool. 2. Add a new webhook (or "HTTP request action" / "Outgoing webhook"). 3. Paste the SourceLoop URL. 4. Set the method to **POST** and the body type to **JSON**. 5. Save. If your tool offers signature verification, **Authentication: None** is correct, the secret is already inside the URL itself. If your tool lets you choose which events trigger the webhook, pick the event that fires after the visitor has shared their email (form submitted, meeting confirmed, chat conversation ended, etc.). Triggering before email is collected will create empty leads that SourceLoop has to drop. ## Step 3: Format the payload You don't need a specific payload shape. SourceLoop auto-detects the email and phone fields no matter how your tool nests them, so most tools' default JSON body works out of the box. If you control the payload (writing custom code, using a tool with a JSON template), the simplest minimal body is: ```json { "email": "lead@example.com", "name": "Jane Doe", "phone": "+1-555-0100", "company": "Acme Co.", "chat_id": "your-unique-submission-id" } ``` Field-level notes: - **`email`** — preferred contact identifier; deduplicated within the workspace - **`phone`** — accepted as a fallback when no email is present - **`name`**, **`company`** — populated on the contact in the Contacts Hub - **`chat_id`** — a unique identifier for this submission. Pass it if your tool may fire the webhook more than once (retries, late-email backstops, etc.) so SourceLoop dedups by it. The Zendesk integration uses `zendesk:{ticket.id}`. - **Any other fields** — stored as custom data on the lead, viewable in the lead detail drawer and exportable to CSV Common form-builder shapes (Typeform's `form_response.answers[]` array, JotForm's `rawRequest`, Tally's `data.fields[]`) are recognised automatically; no transformation needed. ## Step 4: Fire a test webhook The cleanest way to test: trigger your tool the way a real visitor would. 1. Open your tool's form / scheduler / chat in an **incognito window**. 2. Append `?utm_source=test&utm_medium=verify&utm_campaign=webhook-check` to the URL if your tool runs on your own domain. 3. Submit / book / chat with an email you can access. 4. Within seconds, the lead should appear at the top of the **Contacts Hub** at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts). If you'd rather hit the URL directly with curl while testing: ```bash curl -X POST 'https://your-webhook-url-from-setup' \ -H 'Content-Type: application/json' \ -d '{"email":"test@example.com","name":"Webhook Test"}' ``` A successful delivery returns HTTP 200. Any other status means the payload was rejected; check the next section. ## Monitor and debug deliveries Setup -> Incoming Webhooks shows the **Recent Webhook Logs** table at the bottom, every delivery with: - **Type** — which endpoint (web_form / meeting / chat) received it - **Status** — HTTP response code - **Time** — when SourceLoop received the request - **Error** — short message if the payload was rejected The most common errors: | HTTP code | What it means | |---|---| | **200** | Delivered and processed. The lead is in your Contacts Hub. | | **400** | The payload was JSON but neither an email nor phone was found. Either your tool isn't passing those fields, or it's nesting them in a shape SourceLoop hasn't recognised. Paste the raw payload into the **All Events** tab to see what landed. | | **401** | The URL is missing the secret token or the secret has been rotated. Recopy the URL from Setup. | | **404** | Wrong URL entirely, or the endpoint has been deleted. | | **429** | Rate limit hit. Slow the firing rate or contact hello@sourceloop.ai to lift the limit. | | **5xx** | SourceLoop processing failed. Rare; the tool's retry will usually succeed. | ## Where the leads show up Same as every other capture path: - **Contacts Hub** — one row per webhook delivery, with auto-detected contact details, custom data, and full marketing attribution. See [Contacts (Leads) table](/help/contacts-leads-table/). - **Traffic dashboard** — webhook leads roll into the same channel / source / campaign breakdown as every other conversion type. See [Traffic dashboard](/help/traffic-dashboard/). - **Funnels** — build a funnel ending in the webhook's conversion type to find which campaigns produce the most webhook-captured leads. See [Conversion funnels](/help/conversion-funnels-guide/). ## What's next - **Send SourceLoop events outbound:** [How to use SourceLoop Outgoing Webhooks](/help/outgoing-webhooks-overview/) for the reverse direction (SourceLoop → Slack / your backend / Zapier / Make). - **Wire the tracker on your site too:** [Install the SourceLoop tracking pixel](/help/install-the-tracking-pixel/) so the webhook leads pick up real first-touch and last-touch attribution instead of landing as Direct. - **See the Zendesk pattern in practice:** [How to track lead source in Zendesk Chat](/help/track-lead-source-in-zendesk-chat/) walks through the webhook + trigger + automation flow end to end. ## Frequently Asked Questions ### Which tools can use Incoming Webhooks? Any tool that can POST JSON to a URL when a form submission, meeting booking, or chat conversation happens. Examples that ship with native SourceLoop guides include Zendesk Chat (webhook + trigger pattern), Typeform, JotForm, Fillout, Formstack, and 123FormBuilder. For anything else, copy the webhook URL into your tool's webhook or HTTP request action and SourceLoop will accept it. ### Do I need to install the tracking pixel as well? For full marketing source attribution, yes. The tracker on your site records the visitor's session, UTMs, and pre-conversion journey; the webhook delivers the submission with the visitor's anonymous identifier so SourceLoop can stitch the two together server-side. Without the tracker, webhook leads still land in your Contacts Hub, but their source shows as Direct because there's no visitor journey to attach. ### What format does the webhook payload need to be in? JSON, anything else is rejected. SourceLoop auto-detects the email and phone number anywhere in the payload (top level, nested objects, arrays of name/value pairs, common form-builder shapes), so you don't need a specific structure. As long as one identifiable contact field (email or phone) is present, the lead is created. ### How are email addresses detected in the payload? SourceLoop checks known field names first (email, e-mail, email_address, contact_email, user_email, work_email, business_email and their snake_case / camelCase variants), then falls back to any string that matches the email format. The first matched value wins. ### How do I prevent duplicate leads if my tool fires the webhook multiple times? Pass a stable chat_id (or any unique identifier you have for the submission) in the JSON payload. SourceLoop dedups by that identifier within the conversion's website. The Zendesk chat integration uses the literal string zendesk plus the Zendesk ticket id as its chat_id, which is why hourly catch-up automations are safe to re-fire. ### Is the webhook URL secret? What happens if it leaks? The URL contains a secret token unique to your endpoint. Treat it like an API key, don't post it publicly. If it leaks, delete and recreate the endpoint at Setup -> Incoming Webhooks; previously captured leads stay in your Contacts Hub. ### Are there rate limits? Yes. The endpoint accepts up to several requests per second per workspace. For backfilling historical data, contact hello@sourceloop.ai so we can lift the limit for the migration window. ### How do I debug a failing webhook? Open Setup -> Incoming Webhooks and scroll to Recent Webhook Logs. Every delivery is logged with the HTTP status code, timestamp, and any error message. 4xx errors mean the payload was rejected (usually no email/phone found); 5xx means SourceLoop's processing failed (rare, contact support if persistent). --- # How to use SourceLoop Outgoing Webhooks Forward every new or updated SourceLoop lead to your backend, Slack, Zapier, or Make. Up to 5 endpoints, signed deliveries, per-event subscriptions. Source: https://sourceloop.ai/help/outgoing-webhooks-overview/ Updated: 2026-05-29 --- SourceLoop's **Outgoing Webhooks** are the reverse direction of [Incoming Webhooks](/help/incoming-webhooks-overview/). Every time a lead is created or edited in the Contacts Hub, SourceLoop fires a POST to every endpoint you've subscribed to that event type. Use this to alert your team in Slack the moment a lead comes in, hand qualified leads to Zapier / Make for routing, sync custom fields to a homegrown CRM, or trigger any backend workflow you want. ## When to use an Outgoing Webhook Outgoing Webhooks are right when: - You want real-time alerts (Slack, Discord, Microsoft Teams, email tools that accept webhooks) - You're routing leads through Zapier, Make, n8n, Pipedream, or any low-code workflow tool - You're syncing leads into a CRM or backend that SourceLoop doesn't natively support - You need to enrich leads server-side (Clearbit, Apollo, ZoomInfo) before pushing them to your team They're **not** the right fit when: - A native CRM integration covers your use case ([HubSpot](/help/connect-hubspot-to-sourceloop/) / [Salesforce](/help/connect-salesforce-to-sourceloop/) / [Pipedrive](/help/connect-pipedrive-to-sourceloop/) ship with deeper field mapping, status sync, and bidirectional update handling) - You want to pull data on a schedule rather than receive pushes (use the Contacts Hub CSV export or the Conversion API instead) ## What gets sent Each delivery is a JSON POST containing the **full lead record** at the moment of the event. The payload includes everything you'd see on the lead's row in the Contacts Hub: | Field group | Examples | |---|---| | **Identity** | email, name, phone, company, country, city | | **Conversion** | type (Web form / Meeting / Chat / Payment / Subscription / API / CRM Import / Custom), source provider (intercom-webhook / stripe / hubspot-form / etc.), occurred_at, received_at | | **Lifecycle** | status (New / Contacted / In Progress / Converted / Lost), qualified (yes / no / not set), lead score | | **Revenue** | expected_revenue, revenue (in workspace currency) | | **First-touch attribution** | first_channel, first_source, first_medium, first_campaign, first_keyword, first_content, first_landing_page | | **Last-touch attribution** | latest_channel, latest_source, latest_medium, latest_campaign, latest_keyword, latest_content, latest_landing_page | | **Click IDs** | gclid, msclkid, fbclid, li_fat_id, gbraid, wbraid, epik, rdt_cid | | **Technical** | browser, device_type, os | | **Flags** | is_spam, is_duplicate | | **Free text** | notes | | **Custom data** | every field your form / API / CRM sent that doesn't map to the above | And these HTTP headers on every delivery: ``` Content-Type: application/json X-Webhook-Secret: X-Webhook-Event: created | updated ``` The `X-Webhook-Event` value tells your receiver which subscription matched (`created` for New Lead, `updated` for Updated Lead). ## Before you start You'll need: - A **SourceLoop workspace** with at least one captured lead to test against ([free trial](https://app.sourceloop.ai/sign-up)) - **Admin** or **Owner** role in SourceLoop - An HTTPS endpoint that accepts POST requests (Slack incoming webhook, Zapier "Catch Webhook" URL, your own server, etc.) ## Step 1: Open the Outgoing Webhooks page 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Outgoing Webhooks** in the left sidebar. You'll see a form to add a new webhook and (if any exist) a table listing the ones already configured. ## Step 2: Add the endpoint Fill the form: 1. **Webhook URL** — the URL on your receiving server. For Slack, paste an incoming-webhook URL from your workspace's app config. For Zapier / Make, paste the trigger's "Catch Webhook" URL. For your own backend, any HTTPS endpoint that returns 2xx works. 2. **Events** — tick **New Lead**, **Updated Lead**, or both depending on what your downstream needs. 3. **Secret** (optional) — leave blank to have SourceLoop auto-generate one, or paste a string you've chosen. Whatever value lands here is what arrives on the `X-Webhook-Secret` header for every delivery. 4. Click **Add Webhook**. The new endpoint shows up in the table below with its URL, subscribed events, status (Active by default), and a copy button for the secret. > **Workspace cap is 5 endpoints** > If you need to fan out further, point one SourceLoop endpoint at a router (Zapier multi-step Zap, Make scenario, n8n, your own dispatcher) and let it spread the event to as many downstream destinations as you need. This also lets you transform the payload before forwarding. ## Step 3: Verify the receiver works The cleanest way to test: trigger a real lead. 1. Open your site in an **incognito window**. 2. Submit a form, book a meeting, or fire whatever triggers a SourceLoop conversion. 3. Within seconds, your endpoint should receive the POST. If you'd rather test without a real lead, edit any existing lead in the Contacts Hub (change status, add a note) and watch for the **Updated Lead** webhook to fire. To inspect the actual headers and body your endpoint receives, point your URL temporarily at a service like [webhook.site](https://webhook.site) or [RequestBin](https://pipedream.com/requestbin), trigger a lead, and read the captured request. Once you know the shape, switch the URL to your real endpoint. ## Step 4: Sign-verify on your receiver The `X-Webhook-Secret` header carries the exact secret you set (or that SourceLoop generated). On your receiving server, compare the incoming header to the expected value and reject anything that doesn't match. Node.js / Express: ```js app.post("/sourceloop-webhook", express.json(), (req, res) => { const got = req.header("X-Webhook-Secret"); if (got !== process.env.SOURCELOOP_WEBHOOK_SECRET) { return res.status(401).end(); } const event = req.header("X-Webhook-Event"); // "created" or "updated" const lead = req.body; // ...your logic here res.status(200).end(); }); ``` Python / Flask: ```python @app.post("/sourceloop-webhook") def sourceloop_webhook(): got = request.headers.get("X-Webhook-Secret", "") if got != os.environ["SOURCELOOP_WEBHOOK_SECRET"]: return "", 401 event = request.headers.get("X-Webhook-Event") # "created" or "updated" lead = request.json # ...your logic here return "", 200 ``` > **Always check the secret** > Webhook URLs aren't secret on their own. Any backend that knows your URL can POST to it and look like SourceLoop. The X-Webhook-Secret header is what proves the request actually originated from your SourceLoop workspace. ## Common downstream patterns ### Slack alert on every new lead Slack's [incoming-webhook URL](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) accepts a POST body, but it expects Slack's own format (`{text: "..."}`), not SourceLoop's lead JSON. Two options: - **Wrap the lead in a Zap / Make scenario.** Point SourceLoop at a Catch Webhook trigger, transform the body into a Slack message, post it. Cleanest. - **Use a small middleware function.** Cloudflare Worker, Vercel Edge Function, or AWS Lambda that accepts SourceLoop's payload and forwards a Slack-shaped message. ### Zapier / Make routing Zapier's "Webhooks by Zapier -> Catch Hook" trigger accepts SourceLoop's payload as-is. Map the lead fields into the next Zap step (add to Google Sheets, create a task in Asana, send to Mailchimp, whatever). ### Server-side enrichment POST the SourceLoop payload into your backend, look up the company via Clearbit / Apollo / ZoomInfo, then forward the enriched lead onward to your CRM. Use **Updated Lead** as the next-stage trigger so your CRM sees the enriched record. ### Custom CRM sync If your CRM doesn't have a native SourceLoop integration, build a thin shim: receive the POST, map the lead fields to your CRM's contact model, call your CRM's API to upsert. Use the `X-Webhook-Event` header to decide whether to create (`created`) or update (`updated`). ## Monitor and debug deliveries Setup -> Outgoing Webhooks shows a **Recent Webhook Logs** table beneath the webhook list, every delivery with: | Column | What it shows | |---|---| | **URL** | Which endpoint received the request | | **Event** | New Lead or Updated Lead | | **Response code** | HTTP status your endpoint returned | | **Response body** | First few hundred characters of your endpoint's response, useful for reading error messages | | **Error** | Network-level failure (DNS, TLS, timeout) if the request didn't reach your server at all | | **Sent at** | Delivery timestamp | The most common failures: - **401 / 403 from your endpoint** — your server rejected the request. Usually a wrong secret check or missing auth header. Compare the `X-Webhook-Secret` against your expected value. - **404** — wrong URL. Recopy from your downstream tool. - **5xx** — your server errored. Check your application logs and fix the handler; SourceLoop currently doesn't auto-retry, so the missed event won't be sent again unless you re-trigger it (e.g., re-save the lead). - **Network error** — usually a TLS / DNS issue, an HTTPS-only endpoint receiving from an HTTP URL, or a downstream that's been offline too long. ## What's next - **Receive leads into SourceLoop from any tool:** [How to use SourceLoop Incoming Webhooks](/help/incoming-webhooks-overview/) for the reverse direction. - **Native CRM sync instead of webhooks:** [Connect HubSpot](/help/connect-hubspot-to-sourceloop/), [Connect Salesforce](/help/connect-salesforce-to-sourceloop/), or [Connect Pipedrive](/help/connect-pipedrive-to-sourceloop/) cover the bidirectional field-mapping case. - **Browse leads sent by webhooks in the UI:** [Contacts (Leads) table](/help/contacts-leads-table/) walks through the same data your webhook receives. ## Frequently Asked Questions ### How quickly are outgoing webhooks delivered? Webhooks fire within seconds of the triggering event. New lead webhooks fire the moment SourceLoop creates the lead in the Contacts Hub; updated lead webhooks fire whenever an editable field on the lead changes (status, qualified, lead score, expected revenue, revenue, notes, or a custom field). ### What's the difference between New Lead and Updated Lead? New Lead fires once per contact, on the initial conversion (the form submit, meeting booking, chat handle, payment, or CRM import that created the row). Updated Lead fires every time an editable field changes afterwards, manually in the UI, via API, via CRM sync, or by a teammate's edit. Subscribe to one or both depending on what your downstream system needs. ### How do I verify a webhook is really from SourceLoop? Every delivery carries an X-Webhook-Secret header containing the secret you set (or that SourceLoop auto-generated) when you created the endpoint. Compare the header to the expected secret on your receiving server. The header is sent as a literal value, not an HMAC; if you need an HMAC-signed delivery for SOC 2 / PCI compliance, contact hello@sourceloop.ai. ### Will I get the same webhook twice if a field is edited multiple times? Yes, one Updated Lead webhook fires per save. There's no client-side dedup. If you don't want to react to repeat edits, key your downstream logic on the lead's id and the changed fields (the full lead payload is sent each time, so you can diff). ### How many endpoints can I configure? Up to five active webhook endpoints per workspace. Each can subscribe to New Lead, Updated Lead, or both. If you need more, fan out from a single SourceLoop webhook into a router like Zapier or your own backend. ### What happens if my endpoint is down or returns an error? SourceLoop attempts one delivery per event and logs the response in the Recent Webhook Logs table at the bottom of Setup -> Outgoing Webhooks. There's currently no automatic retry on 5xx; build idempotent receivers so that re-sending a missed event later (manually re-saving the lead, or contacting support to replay) is safe. ### Can I limit which leads get sent (e.g., only paid leads, only qualified ones)? Subscribing to events is all-or-nothing per endpoint today. To filter, accept every delivery on your side and ignore the ones that don't match. Common filter fields, lead type (web form / meeting / chat / payment), qualified flag, lead score, status, or any custom field, are all on the payload. ### Where do I see what's been sent? Setup -> Outgoing Webhooks shows a Recent Webhook Logs table with the URL, event, response code, response body, error message, and timestamp for every delivery attempt. Use it to confirm a delivery happened and to diagnose 4xx / 5xx responses from your endpoint. --- # How to connect SourceLoop to Zapier and Make Route SourceLoop leads through Zapier or Make to 6,000+ destinations, Slack, Sheets, Notion, custom CRMs. Both directions, full recipes. Source: https://sourceloop.ai/help/connect-zapier-and-make/ Updated: 2026-05-29 --- [Zapier](https://zapier.com) and [Make](https://www.make.com/en) (formerly Integromat) are the universal connectors between SourceLoop and the long tail of tools customers actually use. If SourceLoop doesn't have a native integration with the thing you want, Zapier or Make almost certainly does, and SourceLoop's webhook surface plugs directly into both. This article covers both directions: sending SourceLoop leads OUT to Zapier / Make for downstream processing, and sending data INTO SourceLoop from Zapier / Make. ## What you can build A few recipes that take less than ten minutes each: | Use case | Direction | Tools involved | |---|---|---| | Slack alert when a new lead comes in | Out | SourceLoop → Zapier/Make → Slack | | Add every lead to a Google Sheet for the sales team | Out | SourceLoop → Zapier/Make → Google Sheets | | Enrich the lead via Clearbit / Apollo / ZoomInfo before sending to your CRM | Out | SourceLoop → Make → Clearbit → Pipedrive (Make is better for this multi-step flow) | | Push leads into a CRM SourceLoop doesn't natively support (Close, Freshsales, Salesflare, Insightly) | Out | SourceLoop → Zapier → CRM | | Add new SaaS signups to a Notion CRM database | Out | SourceLoop → Zapier → Notion | | Send Zoom recordings as leads into SourceLoop | In | Zoom → Zapier → SourceLoop Incoming Webhook | | Ingest leads from a Calendly variant SourceLoop doesn't directly cover | In | Calendly → Zapier → SourceLoop Incoming Webhook | | Forward Intercom conversations to SourceLoop without OAuth | In | Intercom → Zapier → SourceLoop Incoming Webhook | | Score leads with an OpenAI call before they hit the Contacts Hub | In | Form → Zapier → OpenAI → SourceLoop Incoming Webhook | ## Before you start You'll need: - A **SourceLoop workspace** with **Admin** or **Owner** role - A **Zapier** or **Make** account (free tier is fine for low volume) - For outbound recipes: a clear idea of what your downstream destination needs ## Direction 1: SourceLoop → Zapier / Make (outbound) This is the more common direction. SourceLoop fires a webhook every time a lead is created or updated; your Zap or Make scenario catches it and does whatever you want from there. ### Step 1: Get the catch URL from Zapier or Make **In Zapier:** 1. Open [Zapier](https://zapier.com/app/assets/zaps) and click **Create Zap**. 2. In the trigger step, search for and select **Webhooks by Zapier**. 3. Pick **Catch Hook** as the trigger event. Click **Continue**. 4. Click **Continue** on the "Set up trigger" step (no fields to configure). 5. Zapier shows you a **custom webhook URL** that looks like `https://hooks.zapier.com/hooks/catch/.../.../`. Copy it. **In Make:** 1. Open [Make](https://www.make.com/en) and click **Create a new scenario**. 2. Click the first module and search for **Webhooks**. Pick the **Webhooks** app. 3. Choose **Custom webhook** as the trigger. Click to add a webhook, name it (e.g., "SourceLoop leads"), and Make generates the URL. 4. Copy the **custom webhook URL** Make displays. ### Step 2: Wire the URL into SourceLoop 1. Sign in to [SourceLoop](https://app.sourceloop.ai/). 2. Open **Setup -> Outgoing Webhooks** in the left sidebar. 3. Paste the Zapier or Make URL into the **Webhook URL** field. 4. Pick the events you want to subscribe to: - **New Lead** for "do this every time a lead comes in" - **Updated Lead** for "do this every time a lead's status / revenue / fields change" - Both for "react to anything" 5. Set a **Secret** (optional but recommended; auto-generated if you leave it blank). Note it down, you'll verify it on the Zapier / Make side later. 6. Click **Add Webhook**. The new webhook appears in the list, marked Active. ### Step 3: Trigger a test lead The cleanest test: produce a real lead. 1. Submit a form on your tracked site (or pick any existing lead in the Contacts Hub and edit a field for an Updated Lead event). 2. Back in Zapier / Make: - **Zapier**: click **Test trigger**. Zapier reads the most recent delivery and shows you the full payload. - **Make**: with the scenario open, click **Run once**, then trigger the lead. Make catches the payload. 3. Confirm the payload looks right (email, name, attribution fields, etc.). See the [Outgoing Webhooks overview](/help/outgoing-webhooks-overview/) for the full field list. ### Step 4: Add downstream steps From here it's standard Zapier / Make work. Common patterns: **Send a Slack message:** - Zapier: add a "Slack -> Send Channel Message" action. Map `email`, `name`, `latest_channel`, and `latest_campaign` into a message like `🎯 New lead: {{name}} ({{email}}) — from {{latest_channel}} / {{latest_campaign}}`. - Make: add a "Slack -> Create a Message" module with the same mapping. **Append to a Google Sheet:** - Zapier: "Google Sheets -> Create Spreadsheet Row" with one column per field you care about. - Make: "Google Sheets -> Add a Row" similarly. **Push to a CRM:** - Zapier / Make: action to your CRM's "Create or update contact" with email as the dedup key. ### Step 5: Verify the signature (security) SourceLoop sends two custom headers with every delivery: ``` X-Webhook-Secret: X-Webhook-Event: created | updated ``` For Slack / Google Sheets recipes, the security risk is low (the downstream tool only accepts data from your authenticated Zap / scenario). For sensitive recipes (CRM writes, customer-data enrichment, billing changes), add a **Filter** step before any action: - **Zapier**: add a "Filter by Zapier" step. Condition: `X-Webhook-Secret` (under "Headers") equals your expected secret value. Anything else, the Zap halts. - **Make**: add a **Router** with a filter condition checking that the header matches your stored secret. ## Direction 2: Zapier / Make → SourceLoop (inbound) The reverse: a Zap or scenario produces a lead from any source Zapier / Make is connected to (a Slack form, a Notion form, an external CRM event, an OpenAI-scored lead) and pushes it INTO SourceLoop as a new lead. ### Step 1: Get your SourceLoop Incoming Webhook URL 1. In SourceLoop, open **Setup -> Incoming Webhooks**. 2. Copy the URL matching the conversion type you want: - **Web form** for form-style submissions - **Meeting** for scheduling tools - **Chat** for chat / conversation tools See [Incoming Webhooks overview](/help/incoming-webhooks-overview/) for the full reference. ### Step 2: Add a POST step at the end of your Zap or scenario **In Zapier:** 1. Add a final action: **Webhooks by Zapier -> POST**. 2. **URL**: paste your SourceLoop Incoming Webhook URL. 3. **Payload Type**: JSON. 4. **Data**: build the JSON object with at minimum an `email`. Example: ```json { "email": "{{form.email}}", "name": "{{form.name}}", "phone": "{{form.phone}}", "company": "{{form.company}}", "source": "zapier-{{zap.name}}" } ``` 5. **Wrap Request in Array**: No. 6. **Unflatten**: No. **In Make:** 1. Add a final module: **HTTP -> Make a request**. 2. **URL**: paste your SourceLoop Incoming Webhook URL. 3. **Method**: POST. 4. **Body type**: Raw. 5. **Content type**: JSON (application/json). 6. **Request content**: the JSON object with at minimum an `email`. 7. **Parse response**: Yes. ### Step 3: Run and verify Trigger your Zap / scenario, then open the SourceLoop Contacts Hub. The new lead should appear within seconds. If it doesn't: - Check the Zap / scenario's execution log for a 4xx response from SourceLoop (most often "no email found in payload") - Check SourceLoop's **Setup -> Incoming Webhooks -> Recent Webhook Logs** for the delivery and its status ## Common recipes ### "Notify #sales-alerts on Slack when a paid-search lead with revenue > $500 lands" - **Trigger**: Catch Hook (SourceLoop New Lead webhook) - **Filter**: only continue if `latest_channel == "Paid Search"` AND `expected_revenue > 500` - **Action**: Slack → Send Channel Message to `#sales-alerts` - **Cost**: 1 task per matching lead (most Zaps stay under 100/month free if your lead volume is moderate) ### "Add every lead to a Google Sheet, with attribution fields, for the sales weekly review" - **Trigger**: Catch Hook (SourceLoop New Lead) - **Action**: Google Sheets → Create Spreadsheet Row - **Map**: email, name, company, latest_channel, latest_campaign, latest_landing_page, expected_revenue, created_at ### "Enrich via Clearbit, then create Pipedrive contact + deal" (Easier in Make than Zapier because it has more flexible filter/branch logic.) - **Trigger**: Webhooks → Custom webhook (SourceLoop New Lead) - **Step 2**: HTTP → Make a request to Clearbit's Enrichment API - **Step 3**: Filter → only continue if company employee count > 50 - **Step 4**: Pipedrive → Create or Update Person - **Step 5**: Pipedrive → Create Deal, link to the person ### "Score the lead with OpenAI before it lands in SourceLoop" (Inbound direction.) - **Trigger**: Form tool → Zapier (any form tool, or the SourceLoop Incoming Webhook URL is appended via your form's HTTP/webhook action) - **Step 2**: OpenAI → Send Prompt ("Score this lead 1-10 based on email domain and company size") - **Step 3**: Webhooks → POST to SourceLoop Incoming Webhook with the original lead + `ai_score: {{openai.score}}` as a custom field - **Step 4** (optional): If score > 7, also send to Slack ## Pitfalls **Building a loop**: Zap creates leads in SourceLoop → triggers New Lead webhook → fires the same Zap → creates another lead. Always tag Zap-created leads with a custom field and filter them out at the trigger step. **Forgetting the secret check on sensitive recipes**: A Zap that writes to your CRM accepts anyone who knows the URL. Add the filter step that verifies `X-Webhook-Secret`. **Hitting Zapier task limits during traffic spikes**: A blog post going viral can push lead volume 10x for a day. Set up Zapier email alerts when you cross 80% of your monthly task quota. **Using Zapier filters on the wrong step**: Filter steps in Zapier consume a task on every trigger (even when the filter says "skip"). High-volume recipes pay Zapier for every filtered-out delivery. If you have heavy filtering needs, Make's per-operation pricing or a dedicated middleware (Cloudflare Worker, AWS Lambda) is cheaper. ## What's next - **The webhooks that power both directions:** [Outgoing Webhooks overview](/help/outgoing-webhooks-overview/) and [Incoming Webhooks overview](/help/incoming-webhooks-overview/). - **Native CRM integration instead of Zapier:** [Connect HubSpot](/help/connect-hubspot-to-sourceloop/), [Connect Salesforce](/help/connect-salesforce-to-sourceloop/), or [Connect Pipedrive](/help/connect-pipedrive-to-sourceloop/) (deeper field mapping, no Zapier task cost). - **JavaScript SDK for in-page integrations instead of Zaps:** [Install the SourceLoop SDK](/help/install-the-sourceloop-sdk/). ## Frequently Asked Questions ### Do I need a paid Zapier or Make plan? For most simple recipes (a Catch Hook trigger plus one or two action steps), Zapier's free tier (100 tasks per month) and Make's free tier (1,000 operations per month) are enough to start. Heavy lead volume or multi-step zaps will exhaust the free tiers quickly; if you're routing every SourceLoop lead through Zapier, expect to land on the Pro / Make Core plan. ### Should I use Zapier or Make? Both work identically with SourceLoop's webhook surface. Zapier is the easier first choice (simpler UI, broader app catalog, US/EU data residency). Make is cheaper at high volume (per-operation pricing instead of per-task) and supports more complex branching / loops / data transformation natively. If your recipe is "SourceLoop lead -> Slack message", Zapier. If it's "SourceLoop lead -> enrich via Clearbit -> filter by score -> create deal in Pipedrive -> notify the right rep on Slack", Make is usually cheaper and faster. ### How do I know the Zap or scenario actually fired? Both Zapier and Make show per-execution logs (Zapier's "Zap History", Make's "History" tab) with the input payload, every step's output, and any errors. SourceLoop also logs every outbound webhook delivery in the Outgoing Webhooks page so you can confirm SourceLoop sent the event before debugging on the Zapier / Make side. ### What happens if Zapier or Make is down when a lead lands? SourceLoop attempts one delivery per event. If Zapier / Make returns a 5xx or times out, the delivery is logged with the error and no automatic retry happens. To avoid missed events, build a recipe that's idempotent (resending the same lead later doesn't create duplicates) so you can replay missed events manually if needed. ### Can I filter which leads get sent (e.g., only paid leads, only qualified)? Two options. (1) Subscribe to both New Lead and Updated Lead events and use a filter step in your Zap / scenario (Zapier's "Filter by Zapier", Make's "Filter" between modules) to ignore deliveries that don't match. (2) For tighter control, use the lead.type field on the payload to route Web Form leads one way and Payment leads another within the same Zap / scenario. ### How do I prevent infinite loops if my Zap creates leads back in SourceLoop? Tag any lead created from a Zap or Make scenario with a custom field (e.g., a created_via field set to your recipe's name) and add a filter to skip the New Lead webhook for leads carrying that tag. Without the filter, a recipe that takes a SourceLoop lead, enriches it, and writes it back as a new lead in SourceLoop will fire the New Lead webhook, which fires the recipe again, which fires another lead, etc. ### Can I send Zapier or Make data into SourceLoop? Yes. Use Zapier's "Webhooks by Zapier -> POST" action (or Make's "HTTP -> Make a request" module) to POST to your SourceLoop Incoming Webhook URL. This is how you ingest leads from form tools, surveys, or other sources Zapier / Make is already connected to but SourceLoop isn't. --- # Section: API and MCP server --- --- The HTTP API and the MCP server, both reading the same attribution data. The machine-readable specification is at https://sourceloop.ai/help/api/openapi.json. --- --- # Authentication How to create an API key, send it, scope it to the right permissions, and keep it out of your browser bundle. Source: https://sourceloop.ai/help/api/authentication/ --- Every request carries a SourceLoop API key as a bearer token. There is no separate login step, no token exchange, and no expiry to refresh. ```bash curl -s "https://app.sourceloop.ai/api/v1/me" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` Keys look like `slk_live_…`. If you prefer a dedicated header, `X-API-Key` is accepted and behaves identically. Never pass a key in the query string: URLs end up in server logs, browser history, and referrer headers. ## Create a key In the app, go to **Settings, Developers, API keys** and create one. The full key is shown once, at creation. We store only a hash of it, so a lost key is replaced rather than recovered. Two things to decide when you create it: - **Which websites it covers.** A key can be scoped to one website or to every website in the workspace. Agency keys usually cover all of them, so one credential can pull every client. - **Which scopes it carries.** See below. ## Scopes A key only does what its scopes allow. Ask for the narrowest set that makes your integration work, so a leaked reporting key cannot rewrite a customer record or delete a funnel. | Scope | Grants | |---|---| | `metrics:read` | Aggregated metrics, breakdowns, timeseries, paths, products, ad performance, and the outcome catalogue at `/v1/outcomes` | | `conversions:read` | The contact ledger, single contacts, their journeys, and `/v1/changes` | | `conversions:write` | Update a contact's status, value or notes. This one travels: it reaches your CRM and your ad platforms | | `companies:read` | Companies, firmographics, pipeline rollups, company journeys | | `companies:write` | Update company firmographics | | `deals:read` | Deals, pipelines and stages, deal values, deal journeys | | `funnels:read` | List funnel definitions and compute their step conversion, drop-off and breakdowns | | `funnels:write` | Create, edit and delete funnel definitions | | `events:write` | Send server-side events | | `pii:read` | Return real email addresses, phone numbers and names instead of masked ones | `pii:read` is the one to think hardest about, and the one to be deliberate about at creation time: **new keys include it by default**. A key without it still works, but email, phone and name come back masked. Leave it off for anything analytical, and for any key you hand to an AI assistant. See [Data provenance and PII »](/help/api/provenance/). Three endpoints need no scope at all beyond a valid key: `GET /v1/me`, `GET /v1/websites`, and `GET /v1/schema`. ## Check what a key can do `GET /v1/me` answers it directly, which makes it the right first call in any integration and the right thing to log when something returns `403`. ```bash curl -s "https://app.sourceloop.ai/api/v1/me" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` A request that is missing a scope fails with `403` and an `insufficient-scope` problem document naming the scope it wanted. You do not have to guess. ## Where to keep the key Server side only. The API is built for server-to-server and agent use, and a key in a browser bundle is a key in every visitor's devtools. We deliberately do not reflect an origin for browser calls. If you want attribution from browser code, that is what the tracking pixel and the SDK are for. See [Install the SourceLoop SDK »](/help/install-the-sourceloop-sdk/), which uses a public `websiteId` rather than a secret key. ## Rotating and revoking Create the replacement first, deploy it, then revoke the old one. Revocation takes effect immediately, and a revoked key returns `401` on the next call. Every key records when it was last used, so you can tell which are dormant before you remove them. Rate limits are counted per workspace, not per key, so minting a second key does not buy a second budget. See [Errors and rate limits »](/help/api/errors/). --- # Send server-side conversions Attribute events your browser never sees, such as payment webhooks, OAuth callbacks and queue workers, by stitching them to the visitor who caused them. Source: https://sourceloop.ai/help/api/server-side-conversions/ --- Some conversions never touch the browser. A subscription renews on your billing provider's schedule, a trial converts inside a queue worker, a deal closes in your admin panel. `POST /v1/events` is how those still get attributed. Events sent this way are validated and classified with exactly the same rules the browser tracker applies, so a server-side conversion attributes identically to a client-side one. This is not a second, parallel pipeline. ## The one thing that decides whether it works An event needs an identity to attach to. Send **at least one** of: - `anonymous_id`, the visitor's `_sl_aid` cookie value - `email` - `phone` `anonymous_id` is the strong one. It is what stitches a backend event to the browsing history that preceded it, which is the whole point: without it, a purchase that started with a Google Ads click looks like it came from nowhere. So the pattern is: **read the cookie while you still have a request from the visitor, store it with your own record, and send it back later.** ```ts // At signup, in your web app, where you still have the request. const anonymousId = req.cookies["_sl_aid"]; await db.user.update({ where: { id }, data: { anonymousId } }); ``` ```ts // Weeks later, in the billing webhook, where you do not. await fetch("https://app.sourceloop.ai/api/v1/events", { method: "POST", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": invoice.id, }, body: JSON.stringify({ website: "acme.com", events: [ { event_type: "custom", event_name: "subscription_started", anonymous_id: user.anonymousId, email: user.email, occurred_at: new Date(invoice.created * 1000).toISOString(), revenue: invoice.amount_paid / 100, currency: invoice.currency.toUpperCase(), properties: { plan: invoice.plan_name }, }, ], }), }); ``` Send `email` as well when you have it. It gives us a second way to resolve the same person if the cookie was cleared between the visit and the event. ## Getting the details right **Set `occurred_at` from the source event, not from when your worker ran.** A webhook retried three hours later will otherwise land in the wrong day, and a wrong day means the wrong campaign gets the credit. **Send `revenue` in major units** (49.00, not 4900) with an explicit `currency`. Mixed-currency workspaces convert on our side using the workspace currency, and an unlabelled number cannot be converted correctly. **Use `Idempotency-Key`.** Payment providers retry webhooks. Keying on the invoice or charge id means a retry returns the original result instead of double-counting revenue. **Batch up to 200 events per request.** A batch is partial-success: one malformed event is rejected and reported, the rest are accepted. Always read the response counts rather than assuming a `200` means everything landed. ```json { "accepted": 199, "rejected": 1, "errors": [{ "index": 42, "detail": "occurred_at is not a valid timestamp" }] } ``` ## Backfilling history The same endpoint accepts historical events, so you can send the last few months when you first connect. Two limits to plan around: batches cap at 200 events, and the workspace rate limit is counted per minute across every key. Pace the backfill rather than firing it as fast as your loop allows, and watch `RateLimit-Remaining` on the way through. See [Errors and rate limits »](/help/api/errors/). Backfilled events attribute only as well as the identity you send with them. Rows with no `anonymous_id` and no email will land as conversions without a source, which is honest but not useful, so it is usually worth exporting the cookie value alongside the record before you start. ## Checking it worked Call `GET /v1/contacts` filtered to a recent window and look for the contact, or open the Contacts Hub in the app. The conversion should carry a first touch, and that first touch should be the campaign you expect rather than "Direct". If it says Direct, the identity did not resolve. That is almost always a missing `anonymous_id`, and almost never a problem with the event itself. Next: [sync data incrementally »](/help/api/incremental-sync/) to pull outcomes back out. --- # Errors and rate limits The error format, what each status means, the per-plan request budget, and how to back off before you are refused. Source: https://sourceloop.ai/help/api/errors/ --- ## The error format Errors are RFC 9457 problem documents, served as `application/problem+json`: ```json { "type": "https://api.sourceloop.ai/errors/insufficient-scope", "title": "Insufficient scope", "status": 403, "detail": "This key cannot read deals.", "remediation": "Add the deals:read scope to the key, or use a key that has it.", "instance": "req_8f21c0a4" } ``` Two fields are worth building against: - **`remediation`** says what to do next, in a sentence. It is non-standard and deliberate: an error that explains itself costs one round trip, an opaque one costs three. It is also why an AI agent calling this API can usually correct itself without asking you. - **`instance`** is the request id, also returned in the `Sourceloop-Request-Id` header on every response. Quote it in support requests and we can find the exact call. ## What each error means | `type` | Status | What happened | |---|---|---| | `invalid-request` | 400 | A parameter is missing, malformed, or contradicts another | | `unknown-metric` | 400 | That metric does not exist. `GET /v1/schema` lists the ones that do | | `unknown-dimension` | 400 | Same, for a breakdown dimension | | `unknown-filter-operator` | 400 | The filter operator is not supported for that dimension | | `invalid-window` | 400 | The period could not be parsed, or `from` is after `to` | | `window-too-large` | 400 | The requested range exceeds what the endpoint will scan | | `unsupported-combination` | 400 | Each parameter is valid, but not together | | `website-not-found` | 404 | No website matches, or the key does not cover it | | `insufficient-scope` | 403 | The key is valid but lacks a scope. The body names it | | `rate-limit-exceeded` | 429 | Too many requests this minute. Wait `Retry-After` seconds | Where a value was rejected against a fixed set, the problem document also carries an `allowed` array listing the valid values, so you rarely need to open this page. ## Rate limits Counted **per workspace, not per key**. A second key does not buy a second budget. | Plan | Sustained requests per minute | |---|---| | Free | 60 | | Pro | 120 | | Business | 600 | | Agency | 1,200 | Every response carries the current budget, so a well-behaved client slows down before it is refused rather than after: ``` RateLimit-Limit: 600 RateLimit-Remaining: 574 RateLimit-Reset: 41 ``` `RateLimit-Reset` is seconds until the window rolls over. Exceeding the limit returns `429` with `Retry-After` in seconds. Retry after that many seconds, not immediately, and not with a tight loop. Analytics reads are additionally capped at four concurrent queries per workspace. One wide window beats many narrow ones: asking for 90 days once is both faster and cheaper for you than 90 requests for a day each. ## Retrying safely Retry `429` and `5xx`. Do not retry `4xx` other than `429`, because the request itself is what needs changing. Writes accept an `Idempotency-Key` header. Send the same key with a retried write and the original result is returned instead of the write happening twice, which matters when a network timeout leaves you unsure whether the first attempt landed. ```bash curl -s -X POST "https://app.sourceloop.ai/api/v1/events" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" \ -H "Idempotency-Key: signup-8f21c0a4" \ -H "Content-Type: application/json" \ -d '{"events":[{"event_name":"signup_completed","email":"jane@acme.com"}]}' ``` Batch sends are partial-success: one malformed event does not discard the batch, and the response reports accepted and rejected counts separately. --- # Sync data incrementally Poll what changed since your last run instead of refetching a window and diffing, and avoid reacting to your own writes. Source: https://sourceloop.ai/help/api/incremental-sync/ --- If you are building anything that runs on a schedule, a warehouse sync, an alert, a Slack digest, `GET /v1/changes` is the endpoint to build it on. It answers "what changed since my last run" directly, so you do not refetch a rolling window and diff it client-side. The difference is not just tidiness. A window-and-diff job re-reads the same rows every run, gets slower as the workspace grows, and silently misses anything that changed outside the window it happened to pick. ## The loop Store the cursor from each run. Pass it back on the next one. ```ts const state = await loadState(); // { cursor?: string } const url = new URL("https://app.sourceloop.ai/api/v1/changes"); url.searchParams.set("website", "acme.com"); url.searchParams.set("limit", "500"); if (state.cursor) url.searchParams.set("cursor", state.cursor); else url.searchParams.set("since", "2026-08-01T00:00:00Z"); // first run only const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}` }, }); const page = await res.json(); for (const change of page.changes) { await apply(change); } if (page.next_cursor) await saveState({ cursor: page.next_cursor }); ``` On the very first run there is no cursor, so pass `since` instead. With neither, you get the last 24 hours. Keep paging while `meta.has_more` is `true`, and save the cursor only after the page has been applied successfully. Saving it first means a crash mid-page loses those changes permanently. Changes are ordered newest first and retained for 90 days. A job that has been down for longer than that should do a full read rather than trying to resume. ## Filter to what you actually handle ``` ?types=conversion.created,deal.stage_changed ``` Available types include `conversion.created`, `conversion.value_changed`, `stage.changed` and `deal.stage_changed`. Filtering server-side is cheaper than fetching everything and discarding most of it, and it keeps your cursor moving at a sensible rate. ## Do not react to your own writes Every change carries a `source`. When your own job writes back through the API, the resulting change appears in the feed like any other, and a job that reacts to it will trigger itself forever. ```ts for (const change of page.changes) { if (change.source?.startsWith("api:")) continue; // our own write, skip it await apply(change); } ``` Writes made with an API key are stamped `api:`, so you can skip your own specifically rather than skipping every programmatic change. This one line is the difference between a sync that settles and one that loops. ## Scheduling Poll on a schedule that matches how fresh the data needs to be. Every five minutes is plenty for a Slack alert; hourly is plenty for a warehouse. There is no webhook to wait on, which means no endpoint of yours to keep publicly reachable, and no signature to verify. The endpoint needs `conversions:read`. Rate limits are per workspace, so if several jobs poll in parallel, stagger them rather than having them all fire on the hour. See [Errors and rate limits »](/help/api/errors/). ## When to use something else `GET /v1/changes` tells you what moved. It is not the right way to build a report: for aggregate numbers, ask [`/v1/metrics`](/help/api/metrics/) for the window you want, in one call, rather than reconstructing totals from a change feed. A good rule: changes drive **reactions**, metrics drive **reports**. --- # Automate client reporting Pull the same numbers for every client website with one credential, into a spreadsheet, a BI tool, or a scheduled agent. Source: https://sourceloop.ai/help/api/agency-reporting/ --- An agency key can cover every website in the workspace, which means one credential and one loop instead of a login per client. This guide builds the monthly numbers for all of them. ## 1. Find out what the key covers ```bash curl -s "https://app.sourceloop.ai/api/v1/websites" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` Do not hardcode the list. Reading it each run means a newly onboarded client appears in the report without anyone editing a script, and a departed one drops out. ## 2. Ask for the same numbers per website ```ts const websites = await get("/v1/websites"); const rows = []; for (const site of websites.websites) { const m = await get("/v1/metrics", { website: site.domain, period: "last month", metrics: "conversions,revenue,ad_spend,cost_per_lead", group_by: "channel", }); rows.push({ client: site.domain, ...m }); } ``` Two things worth doing here rather than later: **Use `period` rather than computing dates.** `period=last month` resolves in the workspace's own timezone, which is what makes a client's month match what they see in the app. Computing `from` and `to` in your server's timezone is how a report ends up one day off for exactly the clients in another region. **Read `meta.window` back and print it on the report.** It is the range the API actually used. A report that states its own window is one you can defend in a client meeting. ## 3. Pace the loop Rate limits are counted per workspace, so a hundred clients in a tight loop will hit the limit even though each client is small. Watch `RateLimit-Remaining` and pause when it gets low, rather than firing everything and handling `429`s afterwards. ```ts if (Number(res.headers.get("ratelimit-remaining")) < 5) { await sleep(Number(res.headers.get("ratelimit-reset")) * 1000); } ``` Analytics reads are also capped at four concurrent queries per workspace, so a sequential loop is genuinely fine here. Parallelising to 50 will not make the report arrive sooner. ## 4. Land it somewhere **A spreadsheet** is usually the right answer, because it is where the client conversation already happens. Write the rows with the Sheets API from the same script, one row per client per month, and let the sheet own the formatting. **A BI tool** wants the same data appended to a table rather than overwritten, so the history survives. Key on client plus period so a re-run replaces a month instead of duplicating it. **A scheduled agent** can skip the report entirely and answer questions instead. If that is where you are heading, [the MCP server](/help/mcp/) is a better fit than this API: it exposes the same data as tools an assistant can call, so "which clients got worse this month and why" is one question rather than a script. ## Which numbers to put in front of a client Start with conversions, revenue, spend and cost per lead, broken down by channel. Then add the one thing most reports lack: how much of that was attributable at all. Ask [`/v1/metrics`](/help/api/metrics/) for your credited metrics with `group_by=resolution` and the outcomes come back split by why they carried no journey, one row per reason: never matched to a visitor, predating your tracking, or imported from a CRM. That split is what makes the rest trustworthy. A cost per lead computed over 60% of your outcomes is a different claim from one computed over 95%, and stating it up front is far better than having a client discover the gap later. The reasons matter too: contacts that predate tracking shrink on their own, whereas unmatched ones are a live problem worth fixing. ## Keys and access Give the reporting job its own key with `metrics:read` only, and leave `pii:read` off. A monthly summary needs no email addresses, and a key that cannot read them cannot leak them. See [Authentication »](/help/api/authentication/) and [Data provenance and PII »](/help/api/provenance/). --- # Pagination and versioning How to page through list endpoints with cursors, what the meta block tells you, and how the API changes over time. Source: https://sourceloop.ai/help/api/pagination/ --- ## Cursors, not page numbers List endpoints page with an opaque cursor. Read one page, then pass its `next_cursor` back verbatim to get the next one. ```bash # First page curl -s "https://app.sourceloop.ai/api/v1/contacts?website=acme.com&limit=50" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" # Next page curl -s "https://app.sourceloop.ai/api/v1/contacts?website=acme.com&limit=50&cursor=eyJ0IjoiMjAyNi0wOC0xMSJ9" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` Stop when `meta.has_more` is `false`. `next_cursor` is absent on the last page, so a loop that checks for the cursor works just as well. Never construct, decode or edit a cursor. It encodes sort position, not an offset, which is what stops rows shifting under you while you page through a ledger that is still being written to. ## Limits | Endpoint | Default | Maximum | |---|---|---| | `GET /v1/contacts` | 50 | 200 | | `GET /v1/changes` | 100 | 500 | Asking for more than the maximum is not an error; you get the maximum. ## The meta block Every list response carries a `meta` block that describes the page you just received: ```json { "meta": { "website_id": "wst_9a4f", "window": { "from": "2026-07-18T00:00:00Z", "to": "2026-08-16T23:59:59Z" }, "row_count": 50, "total_count": null, "has_more": true, "pii_included": false, "pii_note": "Email, phone and name are masked. Add the pii:read scope to a key to receive them." }, "next_cursor": "eyJ0IjoiMjAyNi0wOC0xMSJ9" } ``` `total_count` is `null` unless you ask for it with `total_count=true`. Counting the whole filtered set costs considerably more than returning a page, so it is opt-in rather than free. When you do ask, the count respects your filters, so "70 of 366" describes the filtered set rather than everything in the workspace. The `window` is the range the API actually resolved, which is the one to display. If you asked for `period=last 30 days`, this is what that meant in the workspace's timezone. ## Versioning The version lives in the path: every endpoint is under `/v1`. A key, a scope and a request shape that work today keep working. Write your client so it tolerates growth: - **Ignore fields you do not recognise.** New fields are added to responses as the product grows, and a client that rejects unknown keys will break on a change that was meant to be harmless. - **Do not depend on field order** in objects, or on the exact wording of a `detail` or `note` string. Match on `type` in error documents instead, which is stable. - **Do not parse cursors**, as above. Anything that would break a correct client, such as removing a field or changing what an existing one means, ships as a new version path rather than being changed underneath `/v1`. Two older paths remain from before the resources were renamed: `/v1/conversions` behaves exactly like `/v1/contacts`, and `/v1/accounts` exactly like `/v1/companies`. They still work and return identical responses. New integrations should use the current names, which are the ones documented in this reference. --- # Data provenance and PII Where each field came from, why that matters before you automate on it, and how personal data is masked when a key lacks permission to read it. Source: https://sourceloop.ai/help/api/provenance/ --- Two properties of this API decide whether an automation you build on it is safe. Both are unusual enough to be worth a page. ## Not every field came from the same place A response mixes three kinds of field, and they carry different authority: | Kind | Where it came from | Safe to act on | |---|---|---| | **Captured** | Observed by SourceLoop directly: the visit, the referrer, the campaign, the form submission, the touch sequence | Yes. This is our own first-hand record | | **Mirrored** | Copied from a system you own, usually your CRM: deal stage, owner, company size, lifecycle status | With care. It is a copy, and it is only as fresh as `synced_at` | | **Derived** | Computed by us from the two above: attributed revenue, cost per lead, model-weighted credit | Yes, but the number depends on the model and window you asked for | Mirrored fields carry a `synced_at` timestamp. Check it before you write back. An automation that reads a stale mirror and pushes a "correction" into the system that actually owns the field will fight that system, and the loser is usually your data. Fields you can write are documented as writable on the endpoint that exposes them, and how far a write travels differs by endpoint. An outcome written through `PATCH /v1/contacts/{id}` does not stop at our copy: it propagates to your connected CRM and to ad-platform conversion upload, which is why it takes an `Idempotency-Key` and why `conversions:write` is a scope you grant deliberately. `PATCH /v1/companies/{id}` is usually a plain local write, because most companies here were discovered from traffic rather than imported, but when the company is linked to a CRM the response carries a `warning` saying the next sync will overwrite what you just set. Deals are read-only over the API: stage and value belong to the system that owns the deal. ## Absent is not zero Where a number is unavailable, it comes back as `null`, not `0`. This is deliberate and it is the mistake most likely to poison an automated report. "No ad account connected" is not "you spent nothing". "This contact has no deal" is not "this deal is worth zero". A dashboard that renders `null` as `0` will quietly show a cost per lead of zero and a founder will make a budget decision on it. Every response also carries a `definitions` block explaining what each metric counts, so a figure from this API is never ambiguous about which day boundary or which model produced it. ## Personal data follows the key Email addresses, phone numbers and names are returned masked unless the calling key carries `pii:read`: ```json { "email": "j•••@acme.com", "phone": null, "name": "J. D." } ``` Masking keeps the email domain, so a masked row still tells you the lead was at `acme.com`. The response says this has happened rather than leaving you to infer it, through `meta.pii_included` and a `pii_note`. **New keys are created with `pii:read` included**, so this is a decision you make when you mint the key, not a protection you get for free. Keep it for integrations that genuinely need identity, such as syncing contacts into a CRM or sending a receipt. Remove it for everything else. Everything else is a longer list than it first looks: reporting jobs, spreadsheet exports, BI syncs, and above all anything connected to an AI assistant. A key handed to [the MCP server](/help/mcp/) sends its responses to a third-party model provider, which may retain them, so that is the case where a key without `pii:read` is worth the small inconvenience. Searching by email or phone counts as reading it, so those filters need the scope too. Two consequences to design around: - **Masked values are not stable identifiers.** Do not use them as a key to join or dedupe on. Use the contact id. - **Turning the scope on changes the response**, not just what you can see. Test your parser against both shapes if the same code path serves keys of both kinds. ## Deleting data Deletion is a support operation rather than an API call, because a delete has to span several stores that do not cascade, and a partial delete is worse than none. Contact us with the record ids and we will handle it as one operation. --- # Workspace Check what a key can do, list the websites it covers, and read the semantic layer: every metric and dimension, plus the outcomes this particular workspace can be measured by. Source: https://sourceloop.ai/help/api/workspace/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | Get key info | GET | `/v1/me` | any key | | List outcomes | GET | `/v1/outcomes` | `metrics:read` | | Get schema | GET | `/v1/schema` | any key | | List websites | GET | `/v1/websites` | any key | Base URL: `https://app.sourceloop.ai/api/v1` ## Get key info `GET /v1/me` What this key is and what it can do Requires: any valid API key. The first call to make. Returns the scopes, plan and websites this key reaches. ### Responses - `200` Key identity - `403` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/me" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/me", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/me", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## List outcomes `GET /v1/outcomes` Which outcomes this workspace can be measured by Requires scope: `metrics:read` The companion to /schema, and the one to read before sending `outcome` or `outcome_values` to /metrics. /schema lists what KINDS of thing can be counted and is identical for every customer. This says which of them THIS workspace has data for, and what its own stage ladder is called, because a CRM ladder is renamed by its own admins and a lead-gen customer has no deals at all. Every outcome is listed, including ones this workspace has never produced. Those come back with `available: false` and an empty `values` list rather than being omitted, so a client can say "you have no deals yet" instead of silently dropping the option. An empty `values` list means one of two things, and `available` tells them apart: conversions and revenue take no narrowing at all, whereas an unavailable outcome has simply produced nothing here. Guessing a stage key instead of reading it here returns an empty result that is indistinguishable from a real zero. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | ### Responses - `200` One entry per outcome, each with its values and availability - `403` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/outcomes?website=acme.com" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/outcomes?website=acme.com", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/outcomes?website=acme.com", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Get schema `GET /v1/schema` Every metric, dimension and filter operator Requires: any valid API key. Generated from the same registry the query compiler uses, so it cannot drift from behaviour. Read this instead of guessing names. ### Responses - `200` Schema document - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/schema" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/schema", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/schema", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## List websites `GET /v1/websites` Websites this key can reach Requires: any valid API key. With each one's timezone and currency, which every other response is expressed in. ### Responses - `200` Websites - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/websites" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/websites", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/websites", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # Metrics Aggregated metrics, breakdowns and timeseries across any dimension, with the attribution model you choose. Source: https://sourceloop.ai/help/api/metrics/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | Get metrics | GET | `/v1/metrics` | `metrics:read` | Base URL: `https://app.sourceloop.ai/api/v1` ## Get metrics `GET /v1/metrics` Aggregate metrics, breakdowns and timeseries Requires scope: `metrics:read` Omit `group_by` for a single total. Add it for one row per dimension value, which also returns the window totals so the share each row represents is visible. Add `granularity` for a timeseries. Note that a total and a timeseries are different queries: unique visitors cannot be summed across days without counting returning people twice, so the aggregate is computed once across the whole window. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | | `period` | string | no | Plain-language period: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Ignored when from/to are given. | | `from` | string (date-time) | no | | | `to` | string (date-time) | no | | | `metrics` | string | yes | Comma-separated. One or more of: visitors, sessions, pageviews, events, conversions, revenue, lifetime_value, customers, avg_ltv, repeat_rate, avg_orders, cohort_mrr, revenue_per_customer, conversion_rate, pageviews_per_session, revenue_per_visitor, spend, impressions, clicks, roas, cac, ltv_cac | | `group_by` | string | no | Split by one dimension, or up to four comma-separated for a nested breakdown such as channel,source. Omit for a single total. One of: channel, source, medium, campaign, utm_term, utm_content, referrer_domain, page, landing_page, hostname, page_title, country, region, city, device_type, browser, operating_system, resolution, platform, ad_campaign, ad_set, ad, ad_platform, keyword, cohort Three of those are answered by a different store and behave differently: - platform and ad_campaign join ad SPEND to the campaign that acquired each customer, which is what makes cac and ltv_cac available. They take one dimension at a time and cannot be crossed with traffic dimensions. - resolution splits outcomes by WHY they had no journey (unmatched, predates tracking, imported contact) and is only available alongside credited metrics. - cohort returns a retention curve instead of a ranking: rows are (cohort, period_index) and the window chooses which cohorts appear, never which payments count. | | `filter` | string[] | no | Repeatable, ANDed. Form: dimension:operator:value. Operators: eq, ne, in, nin, contains, not_contains. Filterable dimensions: channel, source, medium, campaign, utm_term, utm_content, referrer_domain, page, landing_page, hostname, page_title, country, region, city, device_type, browser, operating_system, resolution, event_name, event_type, identified, platform, ad_campaign, ad_set, ad, ad_platform, keyword, campaign_type, match_type, company_domain, crm_stage, crm_account_stage | | `granularity` | string | no | Return a daily time series instead of a single total. Only day is supported: the underlying store buckets by calendar day in the website timezone, so hour, week and month are rejected rather than silently returning days under another label. | | `attribution` | string | no | Credit conversions to a touchpoint using an attribution model instead of counting the event where the conversion fired. One of first_touch, last_touch, linear, u_shaped, time_decay, or up to five comma-separated to compare them in one response, which guarantees every model saw the same window and filters. Requires group_by, and only conversions and revenue can be attributed: a visitor has one channel at a time. When several models are given, each row nests one object per model; a null metric means that model truncated before reaching this value (see meta.truncated_models), which is not the same as zero. | | `outcome` | string | no | WHAT is being counted, which is the question "what do you consider a conversion?" made explicit. Defaults to conversions, the tracked events your script recorded. The rest count a different KIND of object: contact_stage counts PEOPLE reaching a CRM stage, deal_status counts DEALS with the money on them, subscription_status counts subscriptions with MRR, payment_revenue counts individual payments, and revenue counts every money movement from the deduplicated ledger. One request counts ONE kind: 2 MQLs is not a slice of 39 conversions, it is a different object, so the two are never blended in one response. GET /v1/schema describes each with where its numbers come from; GET /v1/outcomes lists this workspace own stage keys. | | `outcome_values` | string | no | Narrows an outcome to specific stages or types, comma separated, using the keys from GET /v1/outcomes. Omit to count every value of that kind, which is what someone asking for their funnel means. Ignored for outcomes that are not workspace-defined. | | `grain` | string | no | WHO an outcome is counted over. A single B2C signup and a six-person B2B buying committee are the same shape in the data and different questions in the business. Only contact_stage offers companies, and the company view is SMALLER than the contact view rather than a slice of it: a company sits at the stage of its most advanced contact. | | `stages` | string | no | Superseded by outcome and outcome_values, which say the same thing in words a caller can discover: this parameter never appeared in GET /v1/schema, so nobody could learn it existed. Still honoured, and outcome wins when both are sent. | | `conjunction` | string | no | How repeated filters combine. Default and. | | `only_paid` | boolean | no | Restrict to paid traffic: a paid medium, or the presence of an ad click id. | | `limit` | integer | no | | ### Responses - `200` Rows plus meta - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/metrics?website=acme.com&period=last%2030%20days" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/metrics?website=acme.com&period=last%2030%20days", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/metrics?website=acme.com&period=last%2030%20days", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # Contacts The lead ledger: list contacts with their first and last touch, read one, and pull the full visitor journey behind it. Source: https://sourceloop.ai/help/api/contacts/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | List contacts | GET | `/v1/contacts` | `conversions:read` | | Get contact | GET | `/v1/contacts/{id}` | `conversions:read` | | Update contact | PATCH | `/v1/contacts/{id}` | `conversions:write` | | Get contact journey | GET | `/v1/contacts/{id}/journey` | `conversions:read` | Base URL: `https://app.sourceloop.ai/api/v1` ## List contacts `GET /v1/contacts` Your contacts, one row per conversion Requires scope: `conversions:read` One row per CONVERSION, not per person: someone who converts twice appears twice. `identity_id` is on every row for callers who need to group by person. Returns your leads with their contact details. New keys include the `pii:read` scope by default, so email and phone come back in full. If a key is created WITHOUT that scope, email, phone and name are masked and the email domain is preserved, so the row still identifies the company. Use that for keys given to contractors, reporting tools, or AI assistants. Paginate with `cursor`, taking `next_cursor` from the previous response. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | | `period` | string | no | Plain-language period: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Ignored when from/to are given. | | `type` | string | no | | | `event_name` | string | no | | | `status` | string | no | | | `email` | string | no | Requires the pii:read scope: searching by email is reading it. | | `include_spam` | boolean | no | | | `cursor` | string | no | | | `limit` | integer | no | | | `total_count` | boolean | no | Include meta.total_count, the exact number of matching rows. Off by default because counting scans every match while the page itself reads one page, so on a large workspace the count costs far more than the rows. Paginate with has_more and next_cursor unless you are rendering "70 of 366". | ### Responses - `200` Conversions - `403` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/contacts?website=acme.com&period=last%2030%20days&type=Web%20Form" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/contacts?website=acme.com&period=last%2030%20days&type=Web%20Form", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/contacts?website=acme.com&period=last%2030%20days&type=Web%20Form", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Get contact `GET /v1/contacts/{id}` One conversion, with its deals and value history Requires scope: `conversions:read` Adds the CRM deals this person is attached to and the full history of value changes, each with the reason and the system that made it. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Responses - `200` Conversion detail - `404` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/contacts/{id}" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/contacts/{id}", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/contacts/{id}", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Update contact `PATCH /v1/contacts/{id}` Write an outcome back Requires scope: `conversions:write` How your CRM tells Sourceloop that a lead became worth 14,000, which turns cost-per-lead reporting into cost-per-revenue reporting. Send an `Idempotency-Key` header. A retried request returns the original result rather than writing twice, and a request that changes nothing writes nothing at all. Both matter because a conversion write propagates to your connected CRM and to ad-platform conversion upload. Attribution fields are computed by Sourceloop and cannot be set. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Headers | Field | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | A unique token per distinct request. Strongly recommended. | ### Request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | no | | | `sales_value` | number | no | | | `quote_value` | number | no | | | `currency` | string | no | | | `notes` | string | no | | | `lead_status_raw` | string | no | | | `lifecycle_stage_raw` | string | no | | | `is_spam` | boolean | no | | ### Responses - `200` Updated, or unchanged - `404` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X PATCH "https://app.sourceloop.ai/api/v1/contacts/{id}" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "won", "sales_value": 14000, "currency": "USD" }' ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/contacts/{id}", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "status": "won", "sales_value": 14000, "currency": "USD" }), }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.patch( "https://app.sourceloop.ai/api/v1/contacts/{id}", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, json={ "status": "won", "sales_value": 14000, "currency": "USD" }, ) data = res.json() ``` ## Get contact journey `GET /v1/contacts/{id}/journey` Every session behind one conversion, in order Requires scope: `conversions:read` The evidence the attribution numbers rest on: how this person arrived each time, and what they did once there. Devices are merged via the identity graph first, so someone who browsed on a phone and converted on a laptop is one timeline rather than two half-journeys. When no raw events exist (a CRM import, or a server-side conversion) the timeline is reconstructed from stored first-touch and last-touch attribution and flagged synthetic:true. Do not present that as a complete history. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `days` | integer | no | | | `limit` | integer | no | | ### Responses - `200` Journey - `404` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/contacts/{id}/journey?days=365&limit=2000" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/contacts/{id}/journey?days=365&limit=2000", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/contacts/{id}/journey?days=365&limit=2000", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # Companies Company records with firmographics, pipeline rollups and attribution, whether or not a CRM is connected. Source: https://sourceloop.ai/help/api/companies/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | List companies | GET | `/v1/companies` | `companies:read` | | Get company | GET | `/v1/companies/{id}` | `companies:read` | | Update company | PATCH | `/v1/companies/{id}` | `companies:write` | | Get company journey | GET | `/v1/companies/{id}/journey` | `companies:read` | Base URL: `https://app.sourceloop.ai/api/v1` ## List companies `GET /v1/companies` Companies, with firmographics, pipeline rollups and attribution Requires scope: `companies:read` Unlike deals, this does NOT require a CRM. A company record comes either from a connected CRM or from our own domain resolution off a captured lead email, and `source` on each row says which. So this answers "which companies are on my site" even with no CRM at all. Personal email domains (gmail and similar) are excluded by default: they are one person, not a company. Pass include_personal=true to keep them. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | | `domain` | string | no | | | `industry` | string | no | | | `country` | string | no | | | `lifecycle_stage` | string | no | | | `has_deals` | boolean | no | | | `has_open_deals` | boolean | no | | | `channel` | string | no | First-touch channel. | | `include_personal` | boolean | no | | | `sort` | string | no | | | `cursor` | string | no | | | `limit` | integer | no | | | `total_count` | boolean | no | Include meta.total_count, the exact number of matching rows. Off by default because counting scans every match while the page itself reads one page, so on a large workspace the count costs far more than the rows. Paginate with has_more and next_cursor unless you are rendering "70 of 366". | ### Responses - `200` Companies - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/companies?website=acme.com" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/companies?website=acme.com", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/companies?website=acme.com", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Get company `GET /v1/companies/{id}` One company, with its deals and its people Requires scope: `companies:read` The account-based view: firmographics, pipeline rollups, attribution, every deal attached to the company, and the people from it aggregated so the same person is not counted twice. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Responses - `200` Company detail - `404` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/companies/{id}" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/companies/{id}", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/companies/{id}", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Update company `PATCH /v1/companies/{id}` Correct a company's firmographics Requires scope: `companies:write` Most companies here were discovered from traffic and identity stitching rather than imported from a CRM, so nobody else owns them and this is a plain local write. When a company IS linked to a CRM connection the write still lands and the response carries a `warning` saying the next sync will overwrite it. Engagement counts, deal rollups and every first_*/latest_* attribution column are computed by Sourceloop and are refused rather than ignored. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Headers | Field | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | | ### Request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `company_name` | string | no | | | `industry` | string | no | | | `size_range` | string | no | | | `employee_count` | integer | no | | | `annual_revenue` | number | no | | | `country` | string | no | | | `region` | string | no | | | `city` | string | no | | | `website_url` | string | no | | | `description` | string | no | | | `linkedin_url` | string | no | | ### Responses - `200` Updated, or unchanged - `403` Something was wrong with the request or the credential. - `404` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X PATCH "https://app.sourceloop.ai/api/v1/companies/{id}" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "industry": "Logistics", "employee_count": 240 }' ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/companies/{id}", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "industry": "Logistics", "employee_count": 240 }), }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.patch( "https://app.sourceloop.ai/api/v1/companies/{id}", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, json={ "industry": "Logistics", "employee_count": 240 }, ) data = res.json() ``` ## Get company journey `GET /v1/companies/{id}/journey` The account journey: everyone at the company, merged Requires scope: `companies:read` Every session by every person at the company, ordered across all of them, because in B2B the person who first read a blog post is rarely the person who signs. people_count reports how many were merged. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `days` | integer | no | | ### Responses - `200` Journey - `404` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/companies/{id}/journey?days=365" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/companies/{id}/journey?days=365", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/companies/{id}/journey?days=365", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # Deals CRM deals and the pipelines they move through, with their values, stages and the marketing behind each one. Source: https://sourceloop.ai/help/api/deals/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | List deals | GET | `/v1/deals` | `deals:read` | | List pipelines | GET | `/v1/pipelines` | `deals:read` | | Get deal | GET | `/v1/deals/{id}` | `deals:read` | | Get deal journey | GET | `/v1/deals/{id}/journey` | `deals:read` | Base URL: `https://app.sourceloop.ai/api/v1` ## List deals `GET /v1/deals` Your pipeline, with the attribution we computed for it Requires scope: `deals:read` Deals mirrored from your CRM, each carrying the first-touch and last-touch attribution Sourceloop derived by walking the touchpoints of everyone linked to the deal. Your CRM does not hold those fields, which is the reason to read deals here rather than from the CRM API. Every row includes `stage.normalized_bucket` (open, won, lost, other). Stage names are chosen by the customer, so a report that trusts the label breaks the day someone renames a stage. Returns a connection-required error, not an empty list, when no CRM is connected. "No deals" and "no CRM" are different answers. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | | `status` | string | no | | | `pipeline_id` | string | no | | | `stage_id` | string | no | | | `account_id` | string | no | | | `owner_email` | string | no | | | `min_amount` | number | no | | | `closed_after` | string | no | | | `closed_before` | string | no | | | `channel` | string | no | First-touch channel that introduced the company. | | `source` | string | no | First-touch source. | | `campaign` | string | no | First-touch campaign. | | `latest_channel` | string | no | | | `sort` | string | no | | | `cursor` | string | no | | | `limit` | integer | no | | | `total_count` | boolean | no | Include meta.total_count, the exact number of matching rows. Off by default because counting scans every match while the page itself reads one page, so on a large workspace the count costs far more than the rows. Paginate with has_more and next_cursor unless you are rendering "70 of 366". | ### Responses - `200` Deals with attribution - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/deals?website=acme.com" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/deals?website=acme.com", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/deals?website=acme.com", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## List pipelines `GET /v1/pipelines` Pipelines and their stages, in order Requires scope: `deals:read` Needed to interpret a deal's stage: which stages exist, what order they run in, their win probability, and which of the customer's stage names actually mean won. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | ### Responses - `200` Pipelines with stages - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/pipelines?website=acme.com" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/pipelines?website=acme.com", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/pipelines?website=acme.com", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Get deal `GET /v1/deals/{id}` One deal, with its people and its history Requires scope: `deals:read` Adds the contacts on the deal, the append-only stage history (which survives a stage being renamed later, so time-in-stage stays computable) and the pipeline it belongs to. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Responses - `200` Deal detail - `404` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/deals/{id}" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/deals/{id}", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/deals/{id}", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Get deal journey `GET /v1/deals/{id}/journey` The marketing behind one deal Requires scope: `deals:read` Every session by every contact attached to the deal. The closed loop stated in full: not "paid search influenced 60,000 of pipeline", but the sessions that claim actually rests on. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `days` | integer | no | | ### Responses - `200` Journey - `404` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/deals/{id}/journey?days=365" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/deals/{id}/journey?days=365", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/deals/{id}/journey?days=365", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # Events Send server-side events for anything the browser cannot see: OAuth callbacks, payment webhooks, queue workers. Source: https://sourceloop.ai/help/api/events/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | Send events | POST | `/v1/events` | `events:write` | Base URL: `https://app.sourceloop.ai/api/v1` ## Send events `POST /v1/events` Send server-side events Requires scope: `events:write` For anything the browser cannot see: OAuth callbacks, payment webhooks, queue workers. Events are validated and classified with exactly the same rules the browser tracker applies, so a server-side conversion attributes identically. Each event needs an email, a phone, or an `anonymous_id` to be attributable. Read `_sl_aid` from the visitor's cookie and pass it as `anonymous_id` to stitch a backend event to their browsing history. Partial success: one malformed event does not discard the batch. ### Headers | Field | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | | ### Request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | | | `events` | object[] | yes | | ### Request body: Event | Field | Type | Required | Description | | --- | --- | --- | --- | | `events[].event_type` | string | no | | | `events[].event_name` | string | no | | | `events[].occurred_at` | string (date-time) | no | | ### Request body: Identity | Field | Type | Required | Description | | --- | --- | --- | --- | | `events[].email` | string | no | | | `events[].phone` | string | no | | | `events[].anonymous_id` | string | no | The visitor's _sl_aid cookie value. | ### Request body: Value | Field | Type | Required | Description | | --- | --- | --- | --- | | `events[].revenue` | number | no | | | `events[].currency` | string | no | | | `events[].properties` | object | no | | ### Responses - `200` Accepted and rejected counts - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X POST "https://app.sourceloop.ai/api/v1/events" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "events": [ { "event_type": "custom", "event_name": "signup_completed", "email": "jane@acme.com", "anonymous_id": "a1b2c3" } ] }' ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/events", { method: "POST", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "events": [ { "event_type": "custom", "event_name": "signup_completed", "email": "jane@acme.com", "anonymous_id": "a1b2c3" } ] }), }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.post( "https://app.sourceloop.ai/api/v1/events", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, json={ "events": [ { "event_type": "custom", "event_name": "signup_completed", "email": "jane@acme.com", "anonymous_id": "a1b2c3" } ] }, ) data = res.json() ``` --- # Ads Spend, impressions and clicks from the connected ad platforms, joined to the conversions they produced. Source: https://sourceloop.ai/help/api/ads/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | Get ad performance | GET | `/v1/ads/performance` | `metrics:read` | Base URL: `https://app.sourceloop.ai/api/v1` ## Get ad performance `GET /v1/ads/performance` Ad platform spend and delivery Requires scope: `metrics:read` `level` is REQUIRED. Spend is stored once per breakdown grain, so a query that does not pin one, or that mixes two, multiplies it. Metrics named `platform_*` are what the ad platform reports under its own attribution window and view-through rules. They will not match Sourceloop attributed conversions, and that difference is expected. Amounts are in the AD ACCOUNT currency, which may differ from the workspace currency; `meta.currency_source` says which you got. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | | `period` | string | no | Plain-language period: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Ignored when from/to are given. | | `level` | string | yes | Grain to report at. "campaign" is the usual view. | | `metrics` | string | no | Comma-separated. One or more of: spend, impressions, clicks, platform_conversions, platform_revenue, ctr, cpc, cpm, platform_roas, platform_conversion_rate | | `breakdown` | boolean | no | One row per item at that level. | | `platform` | string | no | | ### Responses - `200` Ad rows - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/ads/performance?website=acme.com&period=last%2030%20days" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/ads/performance?website=acme.com&period=last%2030%20days", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/ads/performance?website=acme.com&period=last%2030%20days", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # Funnels Define a funnel, then compute it: who reached each step, where they dropped out, and the same broken down by channel. Source: https://sourceloop.ai/help/api/funnels/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | List funnels | GET | `/v1/funnels` | `funnels:read` | | Create funnel | POST | `/v1/funnels` | `funnels:write` | | Get funnel | GET | `/v1/funnels/{id}` | `funnels:read` | | Update funnel | PATCH | `/v1/funnels/{id}` | `funnels:write` | | Delete funnel | DELETE | `/v1/funnels/{id}` | `funnels:write` | | Funnel breakdown | GET | `/v1/funnels/{id}/breakdown` | `funnels:read` | | Compute funnel | GET | `/v1/funnels/{id}/steps` | `funnels:read` | Base URL: `https://app.sourceloop.ai/api/v1` ## List funnels `GET /v1/funnels` List funnels Requires scope: `funnels:read` The funnels defined on this website: their steps, scope, ordering rule, time window and audience filter. A funnel is an ordered list of 2-10 steps. Each step matches either the behavioural stream (pages, events, clicks) or the revenue stream (`source: "revenue"` with a subscription lifecycle event), so a single funnel can run from a page view through to money. NOTE: this path used to serve CRM stage-to-stage conversion. That was a wrapper over the plan GET /v1/metrics already builds, and it held the name of a different feature. For stage conversion use GET /v1/metrics with `metrics=milestones` and `stages`. Two things to remember when you do: a stage a workspace does not use and a stage nobody reached are different answers, and stage counts are not a cohort, so a step rate above 100% is possible and means the later stage was fed from outside the window. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | ### Responses - `200` One row per funnel definition - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/funnels?website=acme.com" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/funnels?website=acme.com", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/funnels?website=acme.com", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Create funnel `POST /v1/funnels` Create a funnel Requires scope: `funnels:write` Define a funnel. `steps` is required and must hold 2 to 10 steps. A behavioural step takes any of `event_type`, `path_matches`, `event_name_matches`, `url_matches`, `click_text_matches`. Use `*` as the wildcard. A revenue step takes `{"source":"revenue","revenue_event":"..."}`. Steps are validated rather than stored as written: a matcher that could never match is rejected, because a funnel is a query every later comparison depends on, and a silent typo shows up months later as a funnel that "stopped working". Mixing behavioural and revenue steps needs `scope: "visitor"`. Revenue is identity-keyed and web signups are anonymous-keyed, so `user` scope does not join them and the funnel collapses to zero after the first step. ### Responses - `200` The created funnel - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X POST "https://app.sourceloop.ai/api/v1/funnels" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Signup to paid", "scope": "visitor", "step_order": "any_order", "filters": { "device_type": "mobile" }, "steps": [ { "name": "Signed up", "conditions": { "event_name_matches": "signup*" } }, { "name": "Started a trial", "conditions": { "source": "revenue", "revenue_event": "subscription_trial_started" } }, { "name": "Converted to paid", "conditions": { "source": "revenue", "revenue_event": "subscription_trial_converted" } } ] }' ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/funnels", { method: "POST", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "name": "Signup to paid", "scope": "visitor", "step_order": "any_order", "filters": { "device_type": "mobile" }, "steps": [ { "name": "Signed up", "conditions": { "event_name_matches": "signup*" } }, { "name": "Started a trial", "conditions": { "source": "revenue", "revenue_event": "subscription_trial_started" } }, { "name": "Converted to paid", "conditions": { "source": "revenue", "revenue_event": "subscription_trial_converted" } } ] }), }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.post( "https://app.sourceloop.ai/api/v1/funnels", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, json={ "name": "Signup to paid", "scope": "visitor", "step_order": "any_order", "filters": { "device_type": "mobile" }, "steps": [ { "name": "Signed up", "conditions": { "event_name_matches": "signup*" } }, { "name": "Started a trial", "conditions": { "source": "revenue", "revenue_event": "subscription_trial_started" } }, { "name": "Converted to paid", "conditions": { "source": "revenue", "revenue_event": "subscription_trial_converted" } } ] }, ) data = res.json() ``` ## Get funnel `GET /v1/funnels/{id}` Get a funnel definition Requires scope: `funnels:read` ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Responses - `200` The funnel - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/funnels/{id}" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/funnels/{id}", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/funnels/{id}", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Update funnel `PATCH /v1/funnels/{id}` Update a funnel Requires scope: `funnels:write` Send only the fields you are changing. Editing steps changes what every historical comparison of this funnel means, so it is a deliberate act rather than a merge. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Responses - `200` The updated funnel - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X PATCH "https://app.sourceloop.ai/api/v1/funnels/{id}" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/funnels/{id}", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.patch( "https://app.sourceloop.ai/api/v1/funnels/{id}", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Delete funnel `DELETE /v1/funnels/{id}` Delete a funnel Requires scope: `funnels:write` Permanent. There is no archive state to fall back on. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Responses - `200` Deleted - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X DELETE "https://app.sourceloop.ai/api/v1/funnels/{id}" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/funnels/{id}", { method: "DELETE", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.delete( "https://app.sourceloop.ai/api/v1/funnels/{id}", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Funnel breakdown `GET /v1/funnels/{id}/breakdown` Funnel split by dimension Requires scope: `funnels:read` Who was dropping out, not just where. A 40% drop-off is not actionable; "12% on desktop and 61% on mobile" is a bug report. Credit for entering and completing is distributed across the dimension values a person touched, per the attribution model. Under `linear`, `position_based` and `time_decay` the counts are fractional, which is correct: a person who touched three channels is not three people. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `period` | string | no | Plain-language period: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Ignored when from/to are given. | | `dimension` | string | no | | | `models` | string | no | Comma-separated attribution models to compare side by side. | | `limit` | integer | no | | ### Responses - `200` One row per dimension value - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/funnels/{id}/breakdown?period=last%2030%20days&dimension=channel&models=first_touch%2Clinear" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/funnels/{id}/breakdown?period=last%2030%20days&dimension=channel&models=first_touch%2Clinear", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/funnels/{id}/breakdown?period=last%2030%20days&dimension=channel&models=first_touch%2Clinear", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` ## Compute funnel `GET /v1/funnels/{id}/steps` Compute a funnel Requires scope: `funnels:read` One row per step: how many people reached it, the two conversion rates, and the median time to get there. `reached` counts people who satisfied this step AND every step before it, so it is non-increasing by construction and a funnel can never widen. `conv_vs_prev` and `conv_vs_first` are different numbers and quoting one as the other is the usual funnel mistake: 40% of the previous step is not 40% of the top. Pass `compare_days=previous` to get the window immediately before this one in the same response, which is the only comparison where both periods are the same length by construction. ### Path parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | | ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `period` | string | no | Plain-language period: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Ignored when from/to are given. | | `compare_days` | string | no | "previous" for the window immediately before, or a number of days to shift back. | ### Responses - `200` One row per step - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/funnels/{id}/steps?period=last%2030%20days&compare_days=previous" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/funnels/{id}/steps?period=last%2030%20days&compare_days=previous", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/funnels/{id}/steps?period=last%2030%20days&compare_days=previous", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # Paths The touch sequences visitors take before they convert. Source: https://sourceloop.ai/help/api/paths/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | Get paths | GET | `/v1/paths` | `metrics:read` | Base URL: `https://app.sourceloop.ai/api/v1` ## Get paths `GET /v1/paths` The touch sequences that lead to outcomes Requires scope: `metrics:read` Which ordered SEQUENCES of touchpoints end in an outcome, and what each is worth. This is what a per-channel breakdown cannot show: the channels that only work together. Journeys longer than `max_touches` keep their first N steps and carry a trailing "..." , so two long journeys that begin the same way group together instead of each becoming a row of one. A path of ["Unattributed"] is an outcome with no journey at all, which is a real outcome rather than missing data. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | | `period` | string | no | Plain-language period: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Ignored when from/to are given. | | `step` | string | no | What each step is labelled with. Default channel. | | `stages` | string | no | Paths to CRM milestones instead of tracked conversions. | | `types` | string | no | | | `events` | string | no | | | `max_touches` | integer | no | | | `limit` | integer | no | | ### Responses - `200` One row per distinct path - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/paths?website=acme.com&period=last%2030%20days" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/paths?website=acme.com&period=last%2030%20days", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/paths?website=acme.com&period=last%2030%20days", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # Products Revenue and conversions broken down by product or plan. Source: https://sourceloop.ai/help/api/products/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | Get products | GET | `/v1/products` | `metrics:read` | Base URL: `https://app.sourceloop.ai/api/v1` ## Get products `GET /v1/products` Per-product sales for a connected store Requires scope: `metrics:read` Revenue, units, orders and margin per product, NET of refunds. Margin covers only the share of revenue whose cost the merchant has set, and `meta.cost_coverage_pct` reports that share. Below 100 the margin describes part of the catalogue rather than all of it. An empty list with `meta.no_data_reason` means no store is connected or no orders were placed, which is not the same as selling nothing. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. acme.com. Required only when the key covers more than one. | | `period` | string | no | Plain-language period: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Ignored when from/to are given. | | `platform` | string | no | | | `limit` | integer | no | | ### Responses - `200` One row per product - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/products?website=acme.com&period=last%2030%20days&platform=shopify" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/products?website=acme.com&period=last%2030%20days&platform=shopify", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/products?website=acme.com&period=last%2030%20days&platform=shopify", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # Changes What changed since your last run. The incremental endpoint automations poll instead of refetching a window. Source: https://sourceloop.ai/help/api/changes/ --- | Endpoint | Method | Path | Permission | | --- | --- | --- | --- | | List changes | GET | `/v1/changes` | `conversions:read` | Base URL: `https://app.sourceloop.ai/api/v1` ## List changes `GET /v1/changes` What changed since your last run Requires scope: `conversions:read` The endpoint automations are built on. Poll it with the cursor from your last run instead of refetching a window and diffing client-side. Every change carries a `source`, so your job can skip its own writes rather than reacting to them and looping. Ordered newest first. Keeps 90 days. ### Query parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `since` | string (date-time) | no | Defaults to the last 24 hours. | | `cursor` | string | no | | | `types` | string | no | Comma-separated: conversion.created, conversion.value_changed, stage.changed, deal.stage_changed | | `limit` | integer | no | | ### Responses - `200` Changes - `400` Something was wrong with the request or the credential. - `429` Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged. ### Example (cURL) ```bash curl -s -X GET "https://app.sourceloop.ai/api/v1/changes" \ -H "Authorization: Bearer $SOURCELOOP_API_KEY" ``` ### Example (Node) ```javascript const res = await fetch( "https://app.sourceloop.ai/api/v1/changes", { method: "GET", headers: { Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`, }, }, ); const data = await res.json(); ``` ### Example (Python) ```python import os, requests res = requests.get( "https://app.sourceloop.ai/api/v1/changes", headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"}, ) data = res.json() ``` --- # MCP tools reference Every tool the SourceLoop MCP server exposes (18 in total), what each one answers, and the arguments it takes. Source: https://sourceloop.ai/help/mcp/tools/ --- Endpoint: `https://app.sourceloop.ai/api/mcp` | Tool | Answers | Permission | | --- | --- | --- | | `list_workspaces` | List workspaces | `metrics:read` | | `get_performance` | Get headline performance | `metrics:read` | | `list_outcomes` | What this workspace can be measured by | `metrics:read` | | `break_down_performance` | Break performance down, and attribute it | `metrics:read` | | `get_ad_performance` | Get ad platform performance | `metrics:read` | | `check_data_health` | Check whether the data can be trusted right now | `metrics:read` | | `explain_metrics` | Explain what a metric means | `metrics:read` | | `find_contacts` | Find the people who converted | `conversions:read` | | `get_deals` | Get deals, and what marketing produced them | `deals:read` | | `get_companies` | Get companies, with their pipeline and engagement | `companies:read` | | `update_contact` | Record what a lead turned out to be worth | `conversions:write` | | `update_company` | Correct a company, or pin its stage | `companies:write` | | `get_journey` | Get the full journey behind a conversion, company or deal | `conversions:read` | | `analyze_ltv` | Analyse lifetime value and payback | `metrics:read` | | `get_funnel` | Compute a funnel the customer built | `funnels:read` | | `get_stage_conversion` | CRM stage-to-stage conversion | `metrics:read` | | `get_products` | Get product-level sales | `metrics:read` | | `get_paths` | Get the touch sequences that lead to conversions | `metrics:read` | ## list_workspaces List workspaces Lists the websites this account can report on, with each one's timezone and currency. Call this first when unsure which website a question refers to, or when another tool reports that the website is ambiguous. Requires scope: `metrics:read` ## get_performance Get headline performance Headline marketing numbers for a period: visitors, conversions, revenue and conversion rate. Use for "how did we do", "how many leads last month", "what is our conversion rate". For a per-channel or per-campaign split, use break_down_performance instead. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `period` | string | no | Period in plain language: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Defaults to the last 30 days. | | `compare_to_previous` | boolean | no | Also return the preceding period of equal length, with the change. Ignored when granularity is set, since a series already shows the movement. | | `granularity` | string | no | Return a TIME SERIES of daily buckets instead of a single total. Use for "show the trend", "conversions per day", or any chart over time. Buckets are calendar days in the website's own timezone, not UTC. Only day is available; to chart weeks or months, take days and sum conversions and revenue, but NOT visitors or sessions, which are unique counts and cannot be added across days. | | `filter` | string | no | Dimension filter, "dimension:operator:value". Operators: eq, ne, in, nin, contains, not_contains. Examples: "channel:in:paid_search,paid_social", "country:eq:US", "campaign:contains:brand". | | `events` | string[] | no | Only count these conversion EVENT names, e.g. ["demo_booked"]. Takes precedence over types. | | `types` | string[] | no | Only count these conversion categories, e.g. ["Web Form","Meeting"]. Coarser than events. | | `only_paid` | boolean | no | Restrict to paid traffic only. | ## list_outcomes What this workspace can be measured by Which kinds of outcome this workspace actually has data for, and its OWN names for the stages behind each one. Call this before any question that mentions stages, statuses, deals or subscriptions. The kinds are fixed — conversions, contacts by stage, deals by status, subscriptions by status, revenue — but WHICH exist is per workspace: a lead-gen customer has no deals at all, and every CRM ladder is renamed by its own admins, so "MQL" may be called something else here. Guessing a stage key returns an empty result that looks like a real zero. Use the returned keys as `outcome` and `outcome_values` on break_down_performance and get_performance. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | ## break_down_performance Break performance down, and attribute it Splits performance by channel, source, campaign, landing page, country, device and similar. Use for "which channels drive leads", "top landing pages", "where does our traffic come from". Pass SEVERAL dimensions to cross them: dimensions=["channel","source"] returns one row per pair, up to four deep. Pass `attribution` to credit conversions across the whole visitor journey instead of counting only the touch where the conversion fired. Pass SEVERAL models to compare them side by side in one answer, which is the honest way to do it: separate calls can land on different windows and the comparison then looks fine and is wrong. Attribution takes ONE dimension at a time, and only conversions and revenue can be attributed, because a visitor was on one channel at a time and has no credit to split. Pass `stages` to break down CRM and lifecycle milestones instead of tracked conversions: stages=["deal_won"] with attribution credits closed-won DEAL revenue to the channels that earned it, which is the closed-loop number a B2B team cannot get from an ad platform. A deal is credited across its whole buying committee, so every contact on it contributes their journey. THREE DIMENSIONS ANSWER SOMETHING DIFFERENT FROM THE REST: - dimension="platform" or "ad_campaign" with metrics like cac or ltv_cac joins ad SPEND to the campaign that acquired each customer, which is how to answer "is this campaign paying for itself". ltv_cac comes back null when the ad account and the ledger are in different currencies, because there is no FX layer and the wrong ratio is a number people switch campaigns off over. - dimension="resolution" splits credited outcomes by HOW their touches were tied to marketing: `click_id` (matched to a specific ad by id), `utm` (matched through UTM parameters), `unresolved` (a real visit tied to no ad), `unattributed` (no visit at all). - dimension="cohort" returns a retention curve instead of a ranking: one row per (cohort, period_index) with revenue and customers at each age. Use analyze_ltv for that rather than this tool. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `dimension` | string | no | What to split by. Use `dimensions` to cross several. Ad dimensions (platform, ad_campaign) unlock cost per acquisition; `resolution` explains unattributed outcomes. | | `dimensions` | string[] | no | Two to four dimensions to cross, e.g. ["channel","source"]. Each row is one combination. Crossing is for traffic dimensions; attribution, ads and cohorts take one at a time. | | `outcome` | string | no | What counts as an outcome. Defaults to `conversions`. ONE request counts ONE kind: 2 MQLs is not a slice of 39 conversions, it is a different object, so never blend them or present a total across them. - conversions (All conversions, from tracking): Every conversion your tracking script recorded: form submissions, meetings booked, chats, payments. One total, no split. - conversion_type (Conversions by type, from tracking): The same conversions, split by what kind they were: form fills, signups, meetings, chats, payments and the rest. Which kinds appear depends on what this workspace records. These add up to the conversions total. - contact_stage (Contacts by stage, from crm): People who reached each stage of your funnel, from your CRM. Counts PEOPLE, not conversions, so these do not add up to the conversions total. Someone now at Customer still counts once under every stage they passed through. - deal_status (Deals by status, from crm): Deals from your CRM, with the money on them. Counts DEALS, not conversions. Pipeline is the amount on deals created; Won revenue is the amount on deals closed won. - subscription_status (Subscriptions by status, from payments): Trials, upgrades to paid, and cancellations from your payment provider, with the recurring revenue each one added or removed. Counts subscriptions, not conversions. - payment_revenue (Payments, from payments): Individual payments you actually received, through Stripe, Shopify or whichever provider you connected. Subscription renewals are not counted here. - revenue (Revenue, from payments): Every money movement we hold for this workspace: payments and refunds from your payment provider, won deals from your CRM, values you typed on a contact, and anything your tracking script reported. Deduplicated so the same money is never counted twice, and credited to channels by the selected attribution model. Call list_outcomes first to learn which of these this workspace has data for, and the stage keys to pass as outcome_values. | | `outcome_values` | string[] | no | Narrow the outcome to specific stages, e.g. ["mql"] or ["deal_won"]. Keys come from list_outcomes. Omit to count every value of that kind, which is what "show me our funnel" means. | | `grain` | string | no | WHO the outcome is counted over. Only contact_stage offers companies. The company view is SMALLER than the contact view rather than a slice of it: a company sits at the stage of its most advanced contact, so three people at one account are one company. Use it when the question is about accounts rather than people. | | `attribution` | string[] | no | One or more attribution models. Several are compared side by side. Total credit is conserved across models, so every model sums to the same conversion count; only the split moves. | | `metrics` | string[] | no | Defaults to visitors, conversions and revenue. With attribution, only conversions and revenue are allowed. | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `period` | string | no | Period in plain language: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Defaults to the last 30 days. | | `filter` | string | no | Dimension filter, "dimension:operator:value". Operators: eq, ne, in, nin, contains, not_contains. Examples: "channel:in:paid_search,paid_social", "country:eq:US", "campaign:contains:brand". | | `events` | string[] | no | Only count these conversion EVENT names, e.g. ["demo_booked"]. Takes precedence over types. | | `types` | string[] | no | Only count these conversion categories, e.g. ["Web Form","Meeting"]. Coarser than events. | | `stages` | string[] | no | DEPRECATED, use `outcome` and `outcome_values`. Still honoured; `outcome` wins when both are sent. | | `only_paid` | boolean | no | Restrict to paid traffic only. | | `limit` | number | no | Rows to return. Default 10, max 100. | ## get_ad_performance Get ad platform performance Ad spend, clicks, impressions and platform-reported conversions from the connected ad accounts. Use for "how much did we spend", "which campaigns are working", "what is our cost per click". A level is REQUIRED because spend is stored once per grain (campaign, ad set, ad, keyword) and mixing grains multiplies it. Figures named platform_* come from the ad platform under ITS OWN attribution rules and will not match Sourceloop attributed conversions; that difference is expected and is not an error. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `level` | string | yes | Grain to report at. Use "campaign" for the usual view, "account" for a per-platform total. | | `metrics` | string[] | no | | | `breakdown` | boolean | no | One row per item at that level, instead of a single total. | | `platform` | string | no | Restrict to one platform, e.g. google_ads or meta_ads. | | `filter` | string | no | Narrow to particular ad entities, "dimension:operator:value". Examples: "campaign:contains:brand", "campaign:in:Search - Brand,Search - Generic". | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `period` | string | no | Period in plain language: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Defaults to the last 30 days. | ## check_data_health Check whether the data can be trusted right now Reports recent tracking activity so a surprising number can be sanity-checked before conclusions are drawn from it. Call this whenever a figure looks wrong, or before recommending a decision based on an unexpected drop. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | ## explain_metrics Explain what a metric means Returns the exact definition of any Sourceloop metric or dimension, and the filter syntax. Use before reporting an unfamiliar metric so it is never described incorrectly, and whenever a user asks how something is calculated. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `metric` | string | no | A metric name. Omit to list everything. | ## find_contacts Find the people who converted The actual leads, not counts: who converted, when, from which channel, and what they turned out to be worth. Use for "who came in from LinkedIn last week", "show me the leads worth over 5000", "which leads has nobody followed up". One row per CONVERSION, so a person who converts twice appears twice; group by identity_id to count people. Channel filters match FIRST touch, because "leads from Google" almost always means "leads Google introduced us to". Email, phone and name are MASKED unless the key carries pii:read, and the domain is kept when masking so a lead at acme.com is still recognisable. Requires scope: `conversions:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `period` | string | no | Period in plain language: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Defaults to the last 30 days. | | `channel` | string | no | First-touch channel, e.g. "paid search". | | `source` | string | no | First-touch source, e.g. "google". | | `medium` | string | no | First-touch medium, e.g. "cpc". | | `campaign` | string | no | First-touch campaign. | | `latest_channel` | string | no | LAST-touch channel: what they came back on, rather than what introduced them. | | `latest_source` | string | no | Last-touch source. | | `latest_campaign` | string | no | Last-touch campaign. | | `type` | string | no | Conversion category, e.g. "Web Form". | | `event_name` | string | no | Exact conversion event, e.g. "demo_booked". | | `status` | string | no | | | `lifecycle_stage` | string | no | CRM lifecycle stage as the CRM spells it. | | `owner_email` | string | no | The CRM owner the contact is assigned to. | | `company_domain` | string | no | Matches on company name. Not personal data, so no pii:read needed. | | `email` | string | no | Search by email. Requires pii:read, because searching by an address IS reading it. | | `phone` | string | no | Search by phone. Requires pii:read. | | `has_value` | boolean | no | true returns only contacts with a sales value recorded. | | `min_sales_value` | number | no | | | `has_quote` | boolean | no | true returns only contacts with a quote value recorded. | | `include_spam` | boolean | no | Default false. Spam is excluded unless asked for. | | `include_duplicates` | boolean | no | Default true. | | `limit` | number | no | Default 25, max 200. | ## get_deals Get deals, and what marketing produced them Deals from the connected CRM with the attribution Sourceloop computed for them, so revenue can be traced back to the channel that started it. Use for "what is in the pipeline", "which channel produces won deals", "how much revenue did paid search actually generate". Set group_by to summarise instead of listing: group_by="first_channel" returns won, open and lost value per channel. TWO WAYS TO CREDIT A DEAL, AND THEY DIFFER: - By default a grouped answer uses the deal record's own first and latest touch, which is the ANCHOR CONTACT's journey. Simple, and it under-counts: two thirds of deals have another contact on them carrying their own browsing history. - Pass `credit` with an attribution model and the answer comes from the journey spine instead, where a deal is one outcome whose touches are the union of the WHOLE buying committee. If the champion arrived from organic search and the VP typed the URL, the anchor view credits Direct alone and this one does not. Deals average 1.83 contacts and reach 3, so this is the normal case rather than an edge one. Deal amounts come from your CRM, not from our tracking, so they are the real numbers your sales team sees. Requires scope: `deals:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `period` | string | no | Period in plain language: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Defaults to the last 30 days. | | `status` | string | no | Restrict to one status. | | `pipeline_id` | string | no | From get_deals results or the pipelines endpoint. | | `stage_id` | string | no | | | `account_id` | string | no | Only deals for one company, from get_companies. | | `owner_email` | string | no | The CRM owner the deal is assigned to. | | `min_amount` | number | no | | | `channel` | string | no | First-touch channel that introduced the deal. | | `source` | string | no | First-touch source. | | `campaign` | string | no | First-touch campaign. | | `latest_channel` | string | no | Last-touch channel. | | `closed_after` | string | no | YYYY-MM-DD. Filters on close date, not creation date. | | `closed_before` | string | no | YYYY-MM-DD. | | `sort` | string | no | Default amount, largest first. | | `group_by` | string | no | Summarise by this instead of listing individual deals. | | `credit` | string | no | Credit won-deal value across the whole buying committee using this attribution model, from the journey spine, instead of the anchor contact's stored first touch. Only with group_by on a channel, source or campaign. | | `limit` | number | no | Default 25, max 200. | ## get_companies Get companies, with their pipeline and engagement The account-based view: which companies are engaging, how many people from each, what pipeline they represent and which channel introduced them. Use for "which accounts are most engaged", "what companies visited but never converted", "show me our biggest accounts by pipeline". Sorted by pipeline value by default, or by engagement when sort="engagement". Company data is not personal data, so this needs no pii:read. Requires scope: `companies:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `sort` | string | no | pipeline = total open + won value, engagement = number of people, recent = last seen. | | `has_deals` | boolean | no | true returns only companies with at least one deal. | | `has_open_deals` | boolean | no | true returns only companies with a deal still open. | | `min_conversions` | number | no | | | `domain` | string | no | Match on company domain, e.g. "acme.com". | | `industry` | string | no | | | `country` | string | no | | | `channel` | string | no | First-touch channel that introduced the company. | | `source` | string | no | First-touch source. | | `include_personal` | boolean | no | Default false. Personal email domains (gmail.com and similar) are not companies and are excluded. | | `limit` | number | no | Default 25, max 200. | ## update_contact Record what a lead turned out to be worth Writes an outcome back onto a contact: the deal value, the status, notes. This is how cost-per-lead reporting becomes cost-per-revenue reporting, because until someone records that a lead closed for 14,000 the attribution has nothing to attribute. Changes propagate to any connected CRM. A write that changes nothing writes nothing. Attribution is computed by Sourceloop and cannot be set. Requires scope: `conversions:write` | Field | Type | Required | Description | | --- | --- | --- | --- | | `contact_id` | string | yes | The contact id, from find_contacts. | | `status` | string | no | Sourceloop status, e.g. "won", "qualified". | | `lifecycle_stage` | string | no | CRM lifecycle stage, as the CRM spells it, e.g. "Customer". Propagates to the connected CRM. | | `lead_status` | string | no | CRM lead status, as the CRM spells it. Propagates to the connected CRM. | | `qualified` | boolean | no | Mark the lead qualified. | | `lead_score` | number | no | | | `sales_value` | number | no | What the deal was actually worth. | | `quote_value` | number | no | | | `currency` | string | no | | | `notes` | string | no | | | `is_spam` | boolean | no | Mark a junk lead, excluding it from reporting. | ## update_company Correct a company, or pin its stage Corrects a company record: firmographics (industry, employee count, country and so on) and its lifecycle stage. Most companies here were discovered from traffic rather than imported, so nobody else owns them and the write simply sticks. When a company IS linked to a CRM the write still lands, and the result says the next sync will overwrite it. Stage is normally DERIVED from deals and contact activity. Setting it PINS the company, stopping that recomputation, and the result says so: do not pin a stage without telling the user that is what happened. Engagement counts, deal rollups and attribution are computed and cannot be set. Requires scope: `companies:write` | Field | Type | Required | Description | | --- | --- | --- | --- | | `company_id` | string | yes | The company id, from get_companies. | | `company_name` | string | no | | | `industry` | string | no | | | `employee_count` | number | no | | | `annual_revenue` | number | no | | | `size_range` | string | no | | | `country` | string | no | | | `region` | string | no | | | `city` | string | no | | | `website_url` | string | no | | | `linkedin_url` | string | no | | | `description` | string | no | | | `lifecycle_stage` | string | no | A stage name from this workspace, e.g. "Customer". Pins the company: stage stops being derived. | ## get_journey Get the full journey behind a conversion, company or deal The timeline the attribution numbers summarise: every session, in order, with how the person arrived each time and what they did once there. Use for "how did this lead find us", "what marketing produced this deal", "show me this account's path". Pass exactly ONE of contact_id, company_id or deal_id. A company or deal journey merges every person attached to it, because in B2B the person who first read a blog post is rarely the person who signs. Devices are merged via the identity graph, so someone who browsed on a phone and converted on a laptop is one timeline, not two half-journeys. When no raw events exist the timeline is reconstructed from stored first-touch and last-touch attribution and marked synthetic:true. Say so rather than presenting it as a complete history. Requires scope: `conversions:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `contact_id` | string | no | From find_contacts. | | `company_id` | string | no | From get_companies. Merges everyone at the company. | | `deal_id` | string | no | From get_deals. Merges everyone on the deal. | | `days` | number | no | How far back to look. Default 365. | | `limit` | number | no | Maximum rows from the event store. Default 2000. | ## analyze_ltv Analyse lifetime value and payback What the customers acquired in a period turn out to be worth, and how long they take to pay back. Use for "what is our LTV", "which channel brings the most valuable customers", "how long until a customer pays for themselves", "what is our repeat rate". THIS IS A COHORT MEASURE AND IT IS NOT REVENUE. The period selects WHO was acquired, not which payments count, so it includes money those customers paid afterwards and grows as they mature. Revenue recorded IN a period is a different question: ask get_performance for that. On a subscription workspace the two differed by 3.1x, so quoting one as the other is a real error. Set `curve: true` for the payback curve: one row per cohort per age, where period_index 0 is the month of acquisition. Set `dimension` to compare acquisition channels. For cost per acquisition and whether a campaign has paid for itself, use break_down_performance with dimension="ad_campaign" and metrics including cac and ltv_cac, which joins the spend the ad platform reports. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `period` | string | no | Period in plain language: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Defaults to the last 30 days. | | `dimension` | string | no | Compare acquisition channels. Omit for one overall figure. | | `curve` | boolean | no | Return the payback curve, one row per cohort per age, instead of a single rollup. | | `grain` | string | no | The AGE unit for the curve, not a calendar bucket. Default month. | | `metrics` | string[] | no | Defaults to lifetime value, customers and average LTV. A curve reports lifetime value, customers and revenue per customer only. | | `limit` | number | no | Rows to return. Default 25, max 100. | ## get_funnel Compute a funnel the customer built Step-by-step conversion for a funnel defined in the app: how many people reached each step and where they dropped out. Use for "how is my signup funnel doing", "where do people drop off", "did the pricing page get worse". Call with no arguments to LIST the funnels this website has, each with its id and steps. Call again with `funnel_id` to compute one. Counts are over TRACKED visitors, and `reached` means someone satisfied that step and every step before it, so the numbers never go up as you move along. Two rates come back and they are different: conv_vs_prev is a share of the previous step, conv_vs_first is a share of the top. Quoting one as the other is the usual mistake. For CRM stages over the whole population including untracked people, use get_stage_conversion instead. Requires scope: `funnels:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `funnel_id` | string | no | Which funnel to compute. Omit to list the available ones first. | | `website` | string | no | Website domain. Only needed when the credential covers more than one. | | `period` | string | no | Plain-language period, e.g. "last 30 days". | | `compare` | boolean | no | Also return the window immediately before, for a like-for-like comparison. | ## get_stage_conversion CRM stage-to-stage conversion How many people or deals reached each CRM STAGE, and the drop-off between them. Use for "what is our lead to customer rate", "where are we losing deals". This is NOT the Funnels feature. For a funnel the customer built in the app -- page views, events, subscription milestones -- use get_funnel. Pass the stages IN ORDER, e.g. stages=["lead","mql","sql","deal_won"]. Each count is distinct entities that ENTERED that stage in the period, so somebody who reached it twice counts once. STAGES ARE COUNTED OVER THE WHOLE POPULATION, including people the tracker never saw: imported contacts, deals closed over the phone, customers who predate the tracking script. That is deliberate, and it is why these rates are lower and more honest than a funnel computed over tracked visitors alone. To credit a stage to the marketing that produced it, use break_down_performance with `stages` and an attribution model instead. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `stages` | string[] | yes | Stage keys in funnel order, e.g. ["lead","mql","customer"] or ["trial_started","trial_converted"]. | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `period` | string | no | Period in plain language: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Defaults to the last 30 days. | ## get_products Get product-level sales What each product sold, refunded and earned, for connected commerce stores. Use for "best selling products", "which products get refunded", "what is our margin by product". Revenue is NET of refunds. Margin is reported only for the share of revenue where the merchant has set a cost, and that share is returned alongside it, so a partial cost catalogue does not read as 100% margin. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `period` | string | no | Period in plain language: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Defaults to the last 30 days. | | `platform` | string | no | Commerce platform. Default shopify. | | `limit` | number | no | Default 25, max 200. | ## get_paths Get the touch sequences that lead to conversions Which SEQUENCES of touchpoints end in an outcome, and what each sequence is worth. Use for "what path do customers take", "how many touches before someone buys", "which combinations of channels work together". This is what a single-channel breakdown cannot show: that paid search rarely closes on its own, or that a particular pair of channels appears before most of the revenue. Long journeys are capped at max_touches and marked with a trailing "..." so two long journeys that begin the same way group together instead of each becoming a row of one. Requires scope: `metrics:read` | Field | Type | Required | Description | | --- | --- | --- | --- | | `website` | string | no | Website domain, e.g. "acme.com". Omit when the account has only one. | | `period` | string | no | Period in plain language: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Defaults to the last 30 days. | | `step` | string | no | What each step in the path is. Default channel. | | `stages` | string[] | no | DEPRECATED, use `outcome` and `outcome_values`. Still honoured; `outcome` wins when both are sent. | | `events` | string[] | no | Only count these conversion EVENT names, e.g. ["demo_booked"]. Takes precedence over types. | | `types` | string[] | no | Only count these conversion categories, e.g. ["Web Form","Meeting"]. Coarser than events. | | `max_touches` | number | no | Steps kept before truncation. Default 5. | | `limit` | number | no | Paths to return. Default 20, max 100. |