Get Started Log in
Docs / Custom HTML sites

Custom HTML sites

This guide covers any site where you control the HTML: hand built sites, static site generators, React, Vue and other single page apps, and server rendered frameworks. The platform guides build on it.

1. Add the script to every page

Copy the tag from Sites, your site, Install and put it in the <head> of every page, including landing pages without forms (the landing page is where the source information is):

<script src="https://cdn.sourcream.ai/t/YOUR_SITE_KEY.js" async></script>

The script is about 6 KB gzipped and loads async, so it never blocks rendering. On pages with a Typeform embed, write defer instead of async, so the script runs before Typeform reads its hidden fields (Typeform and Gravity Forms). Your site settings are built into the script, so changes you save in Sourcream reach visitors within about 10 minutes (the script is cached briefly at the edge and in browsers).

If your site sends a Content Security Policy, allow Sourcream:

script-src https://cdn.sourcream.ai; connect-src https://cdn.sourcream.ai

With the first-party proxy, 'self' already covers both.

2. Forms

With the default settings there is nothing else to do. On every page the script:

  • finds every <form> (the Form selector setting), skipping search forms, forms with method="get" and anything matching the Exclude selector (by default [data-sourcream-ignore], form[role="search"]);
  • fills any input whose name matches a Sourcream field (first_source, last_medium, sc_visitor_id and so on, after any renames in Field names);
  • adds the missing ones as hidden inputs when Auto inject hidden fields is on (the default);
  • watches for forms added later (pop ups, multi step forms, single page app routes) and fills those too.

The values travel with the form submission to wherever the form posts: your own backend, a form service, Zapier, a CRM form handler.

To control placement yourself, paste the hidden fields from the Install tab inside the form and turn Auto inject off:

<input type="hidden" name="first_source">
<input type="hidden" name="first_medium">
<input type="hidden" name="first_campaign">
<input type="hidden" name="last_source">
<input type="hidden" name="last_medium">
<input type="hidden" name="last_campaign">
<input type="hidden" name="sc_visitor_id">

Two other ways to mark where a value goes, useful when a form builder controls the input names:

<input type="hidden" name="field_17" data-sourcream="first_source">
<div class="gfield sc-last_source"><input type="hidden" name="input_4"></div>

An element with data-sourcream="<field>", or with the class sc-<field> on itself or on a Gravity Forms style .gfield wrapper, is filled with that field's value.

To skip a form, add data-sourcream-ignore to it.

3. Lead capture

When a form is submitted, the script refreshes the values and sends the lead to Sourcream in the background (navigator.sendBeacon), without delaying or changing the form's own submission. Sourcream then:

  1. looks up the visitor's durable record (first touch, last touch, journey) on our servers;
  2. stores the lead, counted as one tracked event;
  3. delivers it to every connected integration for that site.

With Capture mode set to Email (the default), the beacon includes the email address (the first type="email" input, or a field whose name contains "email"), and name, first name, last name, phone and company from fields with common names. CRMs need the email to find or create the contact. With Capture mode set to None, no form contents leave the browser; match CRM records by visitor ID instead (see the CRM guides).

Forms that never fire a submit event (some script driven forms send data from a button click), or a conversion that is not a form at all, can report the lead themselves after it succeeds:

window.Sourcream && Sourcream.lead({ email: '[email protected]', name: 'Jo Citizen', company: 'Example Pty Ltd' });

Contact details passed to lead() are only sent when Capture mode is Email.

HubSpot embedded forms that report submissions through HubSpot's onFormSubmitted callback are captured automatically.

4. JavaScript API

The script defines window.Sourcream on every page, even when something goes wrong inside it.

Call What it does
Sourcream.read() { first, last, visitorId }. first and last hold source, medium, campaign, and when present term, content, campaignId, clickIds, landingPage, referrer, ts (epoch ms).
Sourcream.ready(fn) Calls fn once the values are final, after the server has confirmed the visitor. Read them with Sourcream.read(). With consent mode set to Required or Google Consent Mode, it waits until the visitor consents (Consent).
Sourcream.fill(root?) Fills forms now, inside root if given. Use after rendering a form the script could not see.
Sourcream.lead(contact?) Sends a lead now, as described above.
Sourcream.consent(granted) Grants or withdraws consent. See Consent.
Sourcream.visitorId() The visitor ID, the same value as the sc_visitor_id field.
Sourcream.version Script version.

Once values are final the script also pushes { event: 'sourcream_ready', sourcream: { first_source: ..., ... } } to window.dataLayer for Google Tag Manager (GA4 through Google Tag Manager). Like ready(), the push waits for consent: it is not made while the script runs in memory only mode.

5. What is stored in the browser

All first-party, SameSite=Lax, Secure on https, path /:

Cookie Lifetime Holds
_sc_ft 180 days First touch (JSON, URL encoded)
_sc_lt 180 days Last non-direct touch (JSON, URL encoded)
_sc_vid 180 days Visitor ID
_sc_s 30 minutes Session marker, used to decide when to contact our servers
_sc_id 180 days Server set, HttpOnly visitor ID. First-party proxy only.

The script contacts our servers on a new session or a new non-direct visit, not on every page view. The answer carries the canonical first and last touch from our servers, and the script rewrites its cookies from it. That is how a first touch survives Safari deleting script cookies after 7 days.

6. Site settings

Under Sites, your site, Settings:

Setting Use it for
Own domains Other hostnames you own (booking subdomain, landing page domain, staging host). Visits between them are internal, not referrals, and the server accepts data from them. Subdomains of the primary domain are already included. Subdomains and builder hosts such as *.webflow.io are free; any other domain here (acme.com next to acme.com.au, a landing page domain) counts as one more domain on your plan (details).
Cookie domain Set to your apex (for example acme.com.au) when visitors move between subdomains, so one visitor carries one attribution. Never set on shared hosts such as *.webflow.io.
fbclid medium cpc (default), paid_social, or social if most of your Facebook traffic is organic.
Field set Core or extended (Business plan and above).
Field names Rename fields to match your forms or CRM.
Auto inject hidden fields Add missing hidden inputs to forms.
Form selector, Exclude selector Which forms to fill.
Consent mode None, Required, or Google Consent Mode.
Capture mode Email or None.
Ignore referrers Extra hosts to treat as direct, such as a payment provider or booking engine that sends visitors back to you.
Read legacy LeadAttr cookies For sites moving from the LeadAttr script: imports existing lead_attr_first and lead_attr_last cookies and keeps window.LeadAttr.read() working.
Allow localhost Accept data from localhost and 127.0.0.1 while you test.

7. Test it

  1. Open a private window on your live site with ?utm_source=test&utm_medium=test&utm_campaign=install.
  2. In the console, Sourcream.read() returns test / test / install as both first and last touch.
  3. Inspect a form: the hidden fields hold the same values.
  4. Submit the form. Within a few seconds the lead appears under Leads with that source.
  5. Open another page from a Google search result and check the last touch becomes google / organic while the first stays test.

Troubleshooting

  • Sourcream is not defined: the script did not load. Check the tag is in the published page, the key is right, and no Content Security Policy or consent tool blocks it.
  • Everything is (direct) while testing: you arrived from your own site or typed the address. Use a UTM tagged link in a fresh private window.
  • Leads do not appear: look for a failed request to /l in the Network tab. A 403 means the page's domain is not the site's domain or one of its own domains (add staging hosts there; turn on Allow localhost for local testing). If the account has no active plan, the script is served as an empty placeholder (/* sourcream: inactive */) and nothing is sent.
  • Hidden fields stay empty: the form is inside an iframe (a script on your page cannot reach it), or the inputs are rendered after the form selector stopped matching. Call Sourcream.fill() after rendering.

Questions: [email protected]

Stuck on an install? Email [email protected] with your site address and what you are seeing, and we will help you get it working.