Get Started Log in
Docs / WordPress

WordPress

Two ways to install: the Sourcream plugin (recommended, and the only way to get first-party mode without touching the server) or the script tag in your theme.

Option A: the Sourcream plugin

The plugin lives in integrations/wordpress/sourcream. Until it is listed in the WordPress plugin directory, email [email protected] and we send sourcream.zip.

  1. In WordPress, open Plugins, Add New, Upload Plugin, choose sourcream.zip, install and activate.
  2. In Sourcream, open Sites, your site, Install and copy the site key (or the whole script tag; the plugin finds the key in it).
  3. In WordPress, open Settings, Sourcream, paste it and save.
  4. Optional: tick First-party proxy and save. If your site sits behind Cloudflare or a load balancer, also set Trusted proxy header (below).
  5. Press Check connection. It fetches the script the way visitors will and tells you whether Sourcream recognised the key, and in proxy mode whether the proxy route answers.

The plugin adds the script to every front end page with async, needs WordPress 6.3 or later and PHP 7.4 or later, and adds nothing else to your pages. Remove any Sourcream script you added to the theme by hand, so it does not load twice.

First-party proxy

With the proxy on, the script loads from your own site and sends its data through it:

Visitor request Forwarded to
GET /wp-json/sourcream/v1/t/YOUR_SITE_KEY.js https://cdn.sourcream.ai/t/YOUR_SITE_KEY.js?b=/wp-json/sourcream/v1
POST /wp-json/sourcream/v1/c https://cdn.sourcream.ai/c
POST /wp-json/sourcream/v1/l https://cdn.sourcream.ai/l

Only your own site key is served. Only the _sc_id cookie is forwarded; your visitors' other cookies, including WordPress login cookies, never leave your server. The _sc_id cookie Sourcream sets comes back through your own domain, so Safari keeps it for 180 days. The script is cached in a transient for as long as Sourcream allows (5 minutes); data calls are never cached. Why this matters, and its limits: First-party proxy.

Visitor addresses and the trusted proxy header

The proxy also sends Sourcream each visitor's address, in X-Sourcream-Client-IP on collect and lead calls. Sourcream limits how often one visitor can send data (120 collect calls and 10 lead calls a minute, with a visitor on IPv6 counted by the /56 block their address is in), and this is how it tells your visitors apart instead of counting them all as one. Which address the plugin sends depends on the Trusted proxy header setting:

Trusted proxy header Address sent Choose it when
None (the default) The address that connected to your web server Visitors reach your server directly
CF-Connecting-IP Cloudflare's CF-Connecting-IP Every request reaches your site through Cloudflare
X-Forwarded-For The last address in X-Forwarded-For, the one your own proxy added Every request reaches your site through your own load balancer or reverse proxy

A visitor can send any header, so choose a header only when that service sets it on every request. With None, nothing the visitor sends is used. When the chosen header is missing or holds no valid address, the plugin falls back to the address that connected to your server.

If WordPress only sees the address of Cloudflare or of a load balancer and the setting stays on None:

  • behind Cloudflare, every visitor arriving through the same Cloudflare address shares one limit of 120 collect calls and 10 lead calls a minute, and a busy site loses data;
  • behind a load balancer on your own network, its private address counts as no address: there is no per visitor limit, and all your visitors share your site's bucket of 6,000 calls a minute on each endpoint through this server.

Sourcream knows the proxy by the address your server's calls leave from, and over IPv6 by the /56 block that address is in. On shared hosting that address usually serves many other sites, and your site is not kept apart from them: other sites on the same server, and anyone else who sends from that address, count in the same limits as your site, including someone who forges first-party headers that name your site. Those limits are the bucket of 6,000 calls a minute on each endpoint across every site behind the address, your site's bucket for the address (also 6,000 a minute, and in each Sourcream Worker instance a best effort budget of 1,200 collect and 120 lead writes a minute), and the limit of any visitor whose address they name. If they use one up, your tracking calls get 429 for the rest of that minute. Anyone can also send calls through your site's proxy route, and they count in the same limits; set up as above, the plugin sends each sender's own address, so a sender on an ordinary address gets a per visitor limit of its own (senders in one IPv6 /56 share one). A sender calling your route from another Cloudflare Worker arrives with Cloudflare's shared Worker address, which Sourcream does not accept as a visitor address, so through your route it has no per visitor limit: only your site's buckets for your server's address and the hourly caps hold it. Sourcream's hourly caps per site still bound how many new visitors, journey rows and leads anyone can store for your site in an hour (hourly caps).

Your site is kept apart from other sites only when nobody else sends from its identity: an IPv4 address of its own, with your server reaching Sourcream over IPv4 only, or a whole IPv6 /56 of its own. Sourcream answers over IPv6 too, so a server with IPv6 usually connects over it, and a server with an IPv6 address of its own often still shares its /56 with other customers of the host. Your host can tell you which applies. If your site's DNS is on Cloudflare, the Cloudflare Worker proxy is the better option on shared hosting: it is counted by your zone, so other sites never count in its limits (below, shared addresses).

Requirements and gotchas:

  • Pretty permalinks. Settings, Permalinks must be anything except Plain. With Plain permalinks the REST API has no path of its own, and the plugin falls back to the standard script (the settings page says so).
  • Security plugins that block the REST API for visitors who are not logged in also block the proxy. Allow the sourcream/v1 routes. Check connection reports this.
  • Each data call runs PHP. Calls happen on a new session or a new campaign visit, not on every page view, so the load is small, but on a very busy site behind Cloudflare the Cloudflare Worker is lighter and also caches at the edge.
  • Path based blocking. Some filter lists may learn /wp-json/sourcream/. First-party mode reduces blocking; it does not make tracking unblockable.

To test against a staging Sourcream, define SOURCREAM_ORIGIN in wp-config.php (https only, http for localhost). To skip the script on some pages, use the sourcream_enabled filter:

add_filter( 'sourcream_enabled', function ( $enabled ) {
	return $enabled && ! is_page( 'thank-you' );
} );

Using the Cloudflare Worker instead

If your site's DNS is on Cloudflare (its record proxied, orange cloud), Sourcream's Cloudflare Worker proxy can do the plugin proxy's job. That is the better choice on shared hosting: the Worker is counted by your Cloudflare zone instead of your server's shared address, and it takes the PHP load off your server.

  1. Deploy the Worker on your zone with the route www.yourdomain.com/_sc/* (option 1 or 2 in First-party proxy).

  2. In WordPress, open Settings, Sourcream, untick First-party proxy and save, then deactivate the plugin under Plugins. With its proxy off the plugin still adds the standard script, and the page would then load Sourcream twice.

  3. Add the proxied script tag with the theme snippet from Option B, with the Worker's path in place of the standard address:

    add_action( 'wp_head', function () {
        echo '<script src="/_sc/t/YOUR_SITE_KEY.js" async></script>' . "\n";
    }, 1 );
    
  4. Run the checks in First-party proxy: the Network tab shows /_sc/c returning 200 with Set-Cookie: _sc_id=....

The Worker's route is public, like the plugin's: anyone can send calls through it, and they count in your zone's limits (option 1).

Option B: script tag in the theme

Add the tag from the Install tab to the <head> of every page. In a child theme's functions.php (so theme updates do not remove it), or with a code snippets plugin:

add_action( 'wp_head', function () {
	echo '<script src="https://cdn.sourcream.ai/t/YOUR_SITE_KEY.js" async></script>' . "\n";
}, 1 );

This runs in standard mode. For first-party mode without the plugin, use one of the server options in First-party proxy.

Pages with a Typeform embed need the script loaded with defer instead of async, with the plugin or with this tag: Typeform and Gravity Forms.

Forms

The script adds hidden inputs named first_source, first_medium, first_campaign, last_source, last_medium, last_campaign and sc_visitor_id to every form and reports each submission to Sourcream, which delivers it to your CRM. That needs nothing from your form plugin.

Most form plugins only store the fields defined in their own builder, so if you also want the values in the plugin's entries, notification emails or its own CRM add-on, add hidden fields there:

Form plugin What to add
Contact Form 7 [hidden first_source], [hidden first_medium] and so on in the form, and the matching mail tags ([first_source]) in the email body. The names match directly.
Gravity Forms A Hidden field for each value with Appearance, Custom CSS Class set to sc-first_source, sc-last_medium and so on. Gravity Forms names its inputs input_4, so the class tells Sourcream which value goes where. Details: Typeform and Gravity Forms.
Fluent Forms A Hidden Field for each value with its Name Attribute set to first_source and so on.
WPForms A Hidden Field for each value. WPForms names inputs like wpforms[fields][7]; inspect the field on the page and enter that name for the value under the site's Field names in Sourcream. This works when your forms share the same field numbers.
Elementor Pro forms A Hidden field with the ID first_source and so on. Elementor names it form_fields[first_source]; enter that under Field names in Sourcream.

Field names in Sourcream apply to the whole site, so one name per value. After any change, submit a test with UTMs on the URL and check the entry.

Forms rendered inside an iframe (some embedded third party forms) cannot be filled or watched by any script on your page, so Sourcream cannot attribute their submissions. Where the form provider offers an inline (non iframe) embed, use that.

Hourly caps and lead capture

Your site key is public, so anyone can send tracking calls for your site, and anyone can fill your site's hourly caps, whether you use the plugin's proxy, the Cloudflare Worker or the standard script. Once the cap on new visitors (20,000 an hour) or on lead beacons (1,000 an hour) is full, your site's new visitors and lead beacons get 429 until the next UTC hour, and a form submission that only the script reports in that time is lost.

Leads sent to your site's webhook intake URL, and through the API (POST /v1/leads, Agency plan), have a cap of their own (10,000 an hour) that tracking calls never count towards, so they keep arriving while the tracking caps are full. For lead capture that holds up, also send each submission to the intake URL, with the Gravity Forms Webhooks add-on (Typeform and Gravity Forms) or a Zapier step (Zapier, Monday.com and webhooks). When the script and the intake report the same submission, Sourcream merges them into one lead.

Caching and optimisation plugins

The script must not be delayed until user interaction or combined into a bundle. In WP Rocket, LiteSpeed Cache, Autoptimize, Perfmatters, SiteGround Optimizer and similar, exclude the script from "delay JavaScript" and "combine JavaScript". The plugin's script has the id sourcream-js; the URL contains /t/YOUR_SITE_KEY.js. Page caching is fine: the script tag is the same for every visitor.

If a consent plugin blocks scripts until consent, either let it block sourcream-js, or (better) set the site's consent mode in Sourcream and let the script wait for the signal itself. See Consent.

Check it

Open the site in a private window with ?utm_source=test&utm_medium=test&utm_campaign=wordpress, run Sourcream.read() in the browser console, submit a form and check Leads in Sourcream. In proxy mode, the Network tab shows /wp-json/sourcream/v1/c returning 200 with Set-Cookie: _sc_id=....

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.