SourceLoop JavaScript SDK reference
Reference for every method the SourceLoop tracker exposes. Identify visitors, fire custom events, hand checkout metadata to payment providers, refresh SPA forms.
On this page
- Where the SDK is exposed
- Before you start
- window.sourceloop.identify(traits)
- window.sourceloop.track(eventName, properties?)
- window.sourceloop.flush()
- window.sourceloop.getAttribution()
- window.sourceloop.refreshForms()
- window.sourceloop.payment(payload)
- window.sourceloop.checkoutMetadata()
- Reading attribution: window.SourceLoopData
- Configuration: window.SourceLoopConfig
- Common patterns
- Single-page app: track route changes as page views
- Next.js (App Router)
- Identify after login
- Cross-domain stitching
- Server-side ingestion alternative
- Common pitfalls
- What’s next
The SourceLoop tracking script installs a handful of global functions you can call from your own code. Most sites never need to touch them, the tracker handles page views, form submissions, chat conversions, and meeting bookings automatically. This reference is for the cases where you do, custom events, identifying logged-in users, single-page-app routing, custom checkout flows, and so on.
All of these are stable, public API surfaces. Code written against them is safe across tracker updates.
Where the SDK is exposed
The tracker installs three globals:
| Global | What it is |
|---|---|
window.sourceloop | The main public API object with .identify(), .track(), .flush(), .getAttribution(), .refreshForms(), .payment(), .checkoutMetadata() |
window.SourceLoopData | A snapshot of the current visitor’s attribution, useful for read-only access from analytics tools, A/B testing platforms, or your own backend’s session capture |
window.SourceLoopConfig | The configuration object you set before the script loads to control SDK behaviour (websiteId, cross-domain list, debug mode) |
Plus one back-compat helper:
| Global | What it does |
|---|---|
window.SourceLoopFormSubmit(formData, formId, extras?) | A function the tracker exposes so form-builder snippets can fire a Web Form conversion. Used internally by the per-tool integrations; you’ll rarely need it directly unless you’re writing a brand-new integration. |
Before you start
You’ll need:
- The SourceLoop tracking pixel installed on the page where you’re calling these methods.
- A way to defer your code until the tracker has loaded (it loads asynchronously). Either run inside a
DOMContentLoadedlistener, a frameworkuseEffect, or check forwindow.sourceloopin a short retry loop.
window.sourceloop.identify(traits)
Attach traits to the current visitor’s contact record.
Signature:
window.sourceloop.identify(traits: Record<string, unknown>): void
When to call it:
- A user signs up and you have their email + plan
- A user logs in (call on each login so traits stay fresh)
- A user updates their profile inside your app
- A user upgrades / downgrades a plan
Example:
window.sourceloop?.identify({
email: "[email protected]",
name: "Jane Doe",
company: "Acme Co.",
plan: "pro",
signup_source: "homepage_cta",
});
The email field stitches this trait set onto an existing lead (if one exists) or creates a new contact in the Contacts Hub if it’s the first time SourceLoop has seen this visitor. All other fields land as custom data on the contact and are visible in the lead detail drawer.
window.sourceloop.track(eventName, properties?)
Fire a custom event. Useful for product analytics that should sit alongside attribution.
Signature:
window.sourceloop.track(
eventName: string,
properties?: Record<string, unknown>
): void
Examples:
window.sourceloop?.track("video_played", { video_id: "intro", percent: 25 });
window.sourceloop?.track("plan_upgraded", { from: "free", to: "pro" });
window.sourceloop?.track("button_clicked", { id: "hero_signup" });
Custom events are stored on the visitor’s journey timeline (visible in the lead detail drawer once the visitor converts) and can be used as funnel steps in the Funnels builder.
window.sourceloop.flush()
Force any queued events to send immediately, useful right before a hard navigation or page unload.
Signature:
window.sourceloop.flush(): Promise<number>
Resolves with the number of events flushed. The tracker also flushes automatically on visibilitychange and pagehide events, so manual flushing is rarely needed.
Example:
// Before a same-origin hard navigation, ensure the click event was sent.
window.sourceloop?.track("checkout_started", { plan: "pro" });
await window.sourceloop?.flush();
window.location.href = "https://billing.example.com/checkout";
window.sourceloop.getAttribution()
Return the current visitor’s attribution snapshot, or null if the tracker hasn’t initialised yet.
Signature:
window.sourceloop.getAttribution(): AttributionData | null
The returned object includes channel, source, medium, campaign, content, term, landing page, referrer, click IDs, first-touch and last-touch values, and device info. Useful for:
- Surfacing attribution to your own backend at signup time
- Conditionally rendering UI based on the visitor’s channel (e.g., a “Saw our LinkedIn ad?” testimonial for LinkedIn-attributed visitors)
- Integrating with A/B testing tools that need to read the source
Example:
const attr = window.sourceloop?.getAttribution();
if (attr?.channel === "Paid Search") {
document.querySelector(".badge").textContent = "Welcome from " + attr.attribution_source;
}
The same data is also available as a snapshot on window.SourceLoopData for read-only consumers that don’t need the function call form.
window.sourceloop.refreshForms()
Re-scan the DOM for forms and attach the submit listener.
Signature:
window.sourceloop.refreshForms(): void
The tracker scans once on initial page load. If your app injects forms after that (modal opens, route change, lazy-loaded component), call this to make sure new forms get captured.
Example (vanilla):
function openModal() {
document.body.insertAdjacentHTML("beforeend", modalHtml);
window.sourceloop?.refreshForms(); // tell the tracker about the new form
}
Example (React):
function ContactModal() {
useEffect(() => {
window.sourceloop?.refreshForms();
}, []);
return <form>...</form>;
}
window.sourceloop.payment(payload)
Fire a payment_intent event ahead of a payment so the server-side webhook can stitch by email or external_id even when checkout metadata gets stripped between frontend and backend.
Signature:
window.sourceloop.payment(payload: {
email?: string;
externalId?: string;
properties?: Record<string, unknown>;
}): void
Most payment integrations don’t need this, the checkoutMetadata() helper plus the metadata field on the payment provider’s checkout is the higher-fidelity path. Use payment() as a fallback for custom checkouts that can’t carry SourceLoop metadata through (e.g., a server proxy that re-creates the checkout session and loses query strings).
Example:
window.sourceloop?.payment({
email: "[email protected]",
externalId: "order_abc123",
properties: { plan: "pro", amount: 9900, currency: "USD" },
});
window.location.href = "/checkout";
window.sourceloop.checkoutMetadata()
Return an object containing the visitor’s SourceLoop identifier under two key names so it works with any payment provider’s metadata schema.
Signature:
window.sourceloop.checkoutMetadata(): {
sourceloop_anonymous_id: string;
sourceloop_id: string;
}
Pass the whole returned object as the metadata on your payment provider’s checkout (Stripe, Lemon Squeezy, Paddle, Polar, Dodo Payments). When the provider’s webhook fires, SourceLoop reads either key off the payload and stitches the payment back to the right pre-checkout visitor session with 100% confidence.
This is what every payment-tracking article links into. See Track lead source in Stripe for the canonical example.
Example:
const meta = window.sourceloop.checkoutMetadata();
// Stripe Checkout Session (server creates):
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: priceId, quantity: 1 }],
success_url: "/thanks",
cancel_url: "/pricing",
metadata: meta,
subscription_data: { metadata: meta },
});
Reading attribution: window.SourceLoopData
A snapshot of the same object getAttribution() returns, but as a plain property on window. Updated when attribution changes (new session, new referrer detected). Useful for tools that can read globals but don’t call functions (some A/B test setups, some tag-manager templates).
Example:
// Inside Google Tag Manager's custom HTML, or a no-code A/B variant:
const channel = window.SourceLoopData?.channel;
Configuration: window.SourceLoopConfig
Set this before the tracker script loads to configure the SDK’s behaviour. The most common keys:
| Key | Type | Default | What it does |
|---|---|---|---|
websiteId | string (UUID) | required | The SourceLoop website ID for this domain (the tracking snippet contains this) |
debug | boolean | false | Verbose logging in the browser console for troubleshooting |
crossDomains | string[] | [] | List of additional domains the tracker should stitch the visitor across (e.g., main site + checkout subdomain on a different TLD) |
Example:
<script>
window.SourceLoopConfig = {
websiteId: "00000000-0000-0000-0000-000000000000",
debug: false,
crossDomains: ["checkout.example.com", "app.example.com"],
};
</script>
<script async src="https://app.sourceloop.ai/tracking-v3.js"></script>
If you set the config after the tracker has loaded, the changes are ignored.
Common patterns
Single-page app: track route changes as page views
The tracker captures the initial page view automatically but doesn’t watch your router. Call this from your route change handler:
// On every route change
window.sourceloop?.track("pageview", { path: window.location.pathname });
window.sourceloop?.refreshForms(); // also re-scan for new forms
Next.js (App Router)
"use client";
import { usePathname } from "next/navigation";
import { useEffect } from "react";
export function SourceLoopPageView() {
const pathname = usePathname();
useEffect(() => {
window.sourceloop?.track("pageview", { path: pathname });
window.sourceloop?.refreshForms();
}, [pathname]);
return null;
}
Identify after login
async function onLogin(user) {
await myApi.logIn(user);
window.sourceloop?.identify({
email: user.email,
name: user.name,
plan: user.plan,
internal_id: user.id,
});
}
Cross-domain stitching
If your visitor moves from example.com to app.example.com (subdomain stays within the same eTLD+1, the tracker handles this automatically) or to a different domain like example-checkout.com, list the destination in crossDomains:
<script>
window.SourceLoopConfig = {
websiteId: "...",
crossDomains: ["example-checkout.com", "different-billing-domain.io"],
};
</script>
When the visitor clicks a link to a listed domain, the tracker appends a stitching parameter to the URL so the destination site (which must also be running a SourceLoop tracker) recognises the visitor as the same person.
Server-side ingestion alternative
For backend-only events (a server-side checkout, a webhook from an external system you control), POST directly to your Incoming Webhook URL instead of trying to call the JS SDK. The wire format is JSON; auto-extraction handles common payload shapes.
Common pitfalls
Calling the SDK before it’s loaded. window.sourceloop is undefined until the script finishes executing. Always use optional chaining (?.) and consider a short retry-on-load wrapper for code that runs very early in the page lifecycle.
Calling track("form_submit", ...) on a regular form. The tracker already captures form submits as typed Web Form conversions. Doubling up with a track() call creates a duplicate event of the wrong type. Let the tracker handle real submits; reach for track() only for non-conversion events.
Forgetting refreshForms() after a route change. Forms mounted after the initial scan won’t fire conversions when submitted. Call refreshForms() from your router’s transition complete event.
Setting SourceLoopConfig after the tracker has loaded. The tracker only reads the config once, at init. If you set or change the config later, the changes are ignored. Always set window.SourceLoopConfig in a <script> tag immediately before the tracker <script> tag.
What’s next
- Install the tracker if you haven’t already: Install the SourceLoop tracking pixel.
- Verify the tracker is loading and firing: Verify tracking is working.
- Send leads from the backend instead: Incoming Webhooks overview for server-side ingestion.
- Receive every captured lead in your own systems: Outgoing Webhooks overview for real-time forwarding.
Frequently asked questions
-
When is the global window.sourceloop available?
As soon as the tracker script has executed and the page has reached DOMContentLoaded. If you call window.sourceloop.* before that, you may get an undefined error. The safest pattern is to wait for the tracker to load by checking for the global in a short retry loop, or by calling from within a DOM-ready handler.
-
Do I need to call identify for every visitor?
No. The tracker captures the visitor's source, journey, and conversion details automatically. Use identify only when you have additional traits (signup_plan, internal user_id, segment) that you want stored on the contact in addition to what the conversion event captured.
-
What's the difference between track and an automatic form submit?
Track fires a generic custom event with whatever properties you pass. Form submits are captured automatically by the tracker reading the page's submit event, and they create a typed Web Form conversion. Use track for non-conversion events you want to record (button click, video play, plan upgraded, etc.); use form submits for actual lead capture.
-
How does checkoutMetadata work and where do I use it?
It returns an object with sourceloop_anonymous_id and sourceloop_id, the same identifier from two key names so it works with any payment provider's metadata field naming. Pass the object into Stripe / Lemon Squeezy / Paddle / Polar / Dodo Payments checkout metadata so when the webhook from the provider fires, SourceLoop can stitch the payment to the right pre-checkout visitor session.
-
What does refreshForms do?
Re-scans the page for new forms. The tracker scans once on initial load; if your single-page app or React component mounts a form after that (route change, modal open, lazy-loaded component), call window.sourceloop.refreshForms() to re-detect them. Without it, those newly-mounted forms won't fire conversions when submitted.
-
Does SourceLoop's tracker work with React, Vue, Svelte, Next.js, etc.?
Yes. The script is framework-agnostic, it reads the same DOM and submit events any framework produces. The one wrinkle is single-page navigation, where the page URL changes without a full reload. Call window.sourceloop.refreshForms() and (if you want page-view tracking on route changes) trigger a manual page event from your router; see the SPA pattern below.
-
Is there a Node.js or server-side SDK?
Not yet as a packaged SDK. For server-side conversion ingestion, POST directly to your Incoming Webhook URL with the JSON payload. See Incoming Webhooks overview for the wire format.