How SourceLoop stitches identities and deduplicates contacts
How SourceLoop merges multiple browser sessions, devices, and form submissions into one contact. The signals it uses, when merges happen, and how to handle duplicate leads.
On this page
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
- 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.
- 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).
- Thursday: Jane submits a form from her phone with
[email protected]. SourceLoop creates a contact withanonymous_id_Blinked. One row in Leads, one row left in Visitors (the laptop, still anonymous). - Friday: Jane visits your pricing page from her laptop and logs into her account. Your app calls
window.sourceloop.identify({email: "[email protected]"}). SourceLoop sees[email protected]already exists as a contact, and mergesanonymous_id_Ainto 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
- Jane submits a contact form with her work email
[email protected]. Contact created. - Two months later, Jane signs up for the newsletter with her personal email
[email protected]. 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:
- Open the duplicate contact (the one you want to mark, not the canonical one).
- In the Additional Information section, toggle Mark as duplicate.
- 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: trueso 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 [email protected] 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=1on any page) - As the
sourceloop_idfield on every payment integration’scheckoutMetadata()call - In the
anonymous_idfield 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 JavaScript SDK reference → cross-domain stitching 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 [email protected] 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.
- Understand how sessions work alongside identities: How SourceLoop defines a session.
- Learn how the JavaScript SDK exposes identify and identifiers: JavaScript SDK reference.
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 [email protected], and session B on a phone the next day also identifies as [email protected], 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.