Web Pixel
Track web visits, engagement, and conversions from your LinkForty links with a single <script> tag. The web pixel is the browser-side counterpart to the mobile SDKs — where the SDKs attribute app installs, the pixel attributes web sessions and on-site conversions back to the link that drove them.
The web pixel is a LinkForty Cloud feature. It sends events to the Cloud ingestion endpoint and shows up in the Web Analytics dashboard. Self-hosted Core does not include the pixel ingestion pipeline.
What it does
- Page views — every page load, including client-side route changes in single-page apps
- Engagement — time on page, scroll depth, and bounce detection
- Conversions — declarative (
data-lf-event) or programmatic (window.LinkForty.track(...)), with optional revenue values - Link attribution — UTM parameters on the landing URL attribute the session (and its conversions) to the originating link
- Autocapture (opt-in) — outbound clicks, file downloads, and form submissions with no code
- Privacy-first — respects Do Not Track and Global Privacy Control, strips query strings and hashes from URLs, and never stores raw IP addresses
Install
Copy the snippet from your dashboard under Workspace Settings → Web Pixel (it's pre-filled with your workspace's site key), or build it yourself:
<script src="https://cdn.linkforty.com/pixel.js" data-workspace="ws_your_site_key" async></script>
Paste it into the <head> of every page you want to track. That's the entire installation — page views and engagement are tracked automatically.
Your site key (ws_…) lives under Workspace Settings → Web Pixel. It is public and safe to ship in your HTML. See Site key for rotation.
Configuration attributes
All configuration is read from data-* attributes on the script tag:
| Attribute | Required | Default | Description |
|---|---|---|---|
data-workspace | Yes | — | Your public workspace site key (ws_<32 hex chars>). Routes events to your workspace. |
data-endpoint | No | https://api.linkforty.com/api/pixel/v1/event | Override the ingestion endpoint (e.g. when routing through a custom domain). |
data-spa | No | true | Single-page-app route tracking. Set "false" to disable and send only the initial page view. |
data-autocapture | No | false | Set "true" to auto-track outbound clicks, downloads, and form submits. |
data-visitors | No | false | Set "true" to assign a persistent visitor ID for new-vs-returning analytics. Consent-sensitive — see Visitor ID. |
Example with everything enabled:
<script src="https://cdn.linkforty.com/pixel.js"
data-workspace="ws_a1b2c3d4e5f6789abcdef0123456789a"
data-autocapture="true"
data-visitors="true"
async></script>
Tracking conversions
A conversion is any meaningful action you want to attribute back to a link — a signup, a purchase, a demo request. There are two ways to fire one.
Declarative (no JavaScript)
Add data-lf-event to any clickable element. When the element (or anything inside it) is clicked, the pixel fires a conversion with that name. Add data-lf-value for a numeric value such as revenue.
<button data-lf-event="signup_completed">Sign up</button>
<button data-lf-event="checkout_completed" data-lf-value="49.00">
Complete purchase
</button>
| Attribute | Description |
|---|---|
data-lf-event | Conversion event name (max 255 chars). Fired on click. |
data-lf-value | Optional numeric value (e.g. revenue). Non-numeric values are ignored. |
Programmatic
Call window.LinkForty.track() from your own code — for conversions that happen after an async step (payment confirmation, server response):
window.LinkForty.track('checkout_completed', { revenue: 49.0, plan: 'pro' });
window.LinkForty.track(eventName: string, properties?: Record<string, unknown>): void
eventName— the conversion name (max 255 chars).properties— optional flat object of custom data. Values may be primitives or arrays; objects are flattened out. Limited to 30 keys, 512 characters per string. Put revenue in arevenueproperty to have it counted in Web Analytics goals & revenue.
Tracking before the pixel loads
Because the script is async, it may not be ready when your code runs. Queue calls on window.LinkForty.q and the pixel replays them on load:
window.LinkForty = window.LinkForty || { q: [] };
window.LinkForty.q.push(['signup', { plan: 'pro' }]);
JavaScript API
The pixel exposes a small global on window.LinkForty:
| Member | Type | Description |
|---|---|---|
track(name, properties?) | function | Fire a conversion event imperatively. |
version | string | The loaded pixel version (e.g. "1.2.3"). |
q | array | Pre-load queue — push [name, properties] tuples here before the pixel loads. |
Event types
Every event the pixel sends is one of three types:
| Type | When it fires | Key data |
|---|---|---|
page_view | Initial load and each SPA route change (debounced, de-duplicated by URL) | page title, language, URL, referrer, UTM |
engagement | Once when the tab is hidden or the page unloads | time on page, scroll depth, bounce |
conversion | data-lf-event clicks, track() calls, and autocapture | event name, value, custom properties |
All events also carry the page URL (query string and hash removed), the referrer (likewise trimmed), parsed UTM parameters, and the session/visitor IDs when available.
Transport
Events are sent with navigator.sendBeacon() (falling back to fetch with keepalive) as a POST to the ingestion endpoint with Content-Type: text/plain. This keeps the request CORS-safelisted (no preflight) and lets it complete even as the page unloads. The pixel adds no measurable load to your page and ships in well under 10 KB gzipped.
SPA route tracking
For single-page apps (React, Vue, Next.js, SvelteKit, etc.), the pixel automatically tracks client-side navigation. It patches the History API (pushState/replaceState) and listens for popstate (back/forward), then fires a page_view for each new route. Rapid transitions are debounced and duplicate URLs are ignored; hash-only changes are not counted as new views.
Session-scoped attribution (last-click). The UTM parameters on the page where a visitor lands are captured as the session origin and stored per-tab in sessionStorage. As the visitor navigates to UTM-less routes within your app, conversions still attribute to that originating link. If a later route carries new UTM parameters, it supersedes the origin (last-click wins).
Set data-spa="false" to turn this off — the pixel will then send only the initial page view.
Autocapture
Set data-autocapture="true" to track common interactions without writing any code. Three interaction types are captured, each sent as a conversion:
| Event name | Trigger | Properties |
|---|---|---|
outbound_click | A link to a different host (http/https) | href, host |
file_download | A link with a download attribute or a known file extension (pdf, zip, dmg, csv, docx, mp4, …) | href, ext |
form_submit | Any <form> submission | formId, formName, action (when present) |
Autocapture is off by default. Your own data-lf-event and track() conversions work the same whether or not autocapture is enabled.
Visitor ID (opt-in)
By default the pixel uses an in-memory, per-tab session ID only — there is no durable identifier. Set data-visitors="true" to additionally assign a persistent visitor ID (v_…) stored in localStorage. This powers new-vs-returning visitor analytics.
A persistent visitor ID is the one durable identifier the pixel can create. Only enable data-visitors="true" where you have the appropriate user consent (GDPR/CCPA). If localStorage is unavailable (private browsing, blocked), no visitor ID is sent and tracking continues without it.
Privacy
The pixel is built to be privacy-respecting by default:
- Do Not Track / GPC — if
navigator.doNotTrackis"1"/"yes", ornavigator.globalPrivacyControlistrue, the pixel disables itself.track()becomes a no-op and no events are sent. - No raw IP storage — the ingestion endpoint uses the request IP only to derive coarse geolocation (country, region, city, timezone) and then discards it. The raw IP is never written to the database.
- Trimmed URLs — query strings and hash fragments are stripped from the page URL and referrer before sending. (UTM parameters are parsed out separately for attribution.)
- Bounded payloads — custom properties are flattened and capped (30 keys, 512 chars per value) so you can't accidentally exfiltrate large or nested PII.
Site key
Your site key identifies your workspace to the ingestion endpoint.
- Format:
ws_followed by 32 hex characters (e.g.ws_a1b2c3d4e5f6789abcdef0123456789a). - Public: it lives in your page's HTML, so treat it as public — it grants the ability to send events to your workspace, nothing more.
- Separate from your App Token. The mobile SDK
appToken(at_…) and the web site key (ws_…) are independent. Rotating one never affects the other — important because an app token is baked into shipped app binaries (effectively non-rotatable), while a web key sits in scrapeable HTML and must be cheaply rotatable.
Rotating the site key
If your key is being abused (spam events from a scraped snippet), rotate it from the dashboard under Workspace Settings → Web Pixel → Rotate site key. Rotation is an owner-only action performed from your logged-in session. The old key stops working immediately, so update your <script> tag with the new key right after rotating.
Custom ingestion domain
By default the pixel sends events to LinkForty Cloud at api.linkforty.com. If you route LinkForty through a custom domain, point the pixel at that domain's ingestion endpoint with data-endpoint:
<script src="https://cdn.linkforty.com/pixel.js"
data-workspace="ws_your_site_key"
data-endpoint="https://app.yourcompany.com/api/pixel/v1/event"
async></script>
Troubleshooting
No events showing up
- Confirm
data-workspaceis your real site key (not thews_…placeholder). - Check the browser console/network tab for a
POSTto…/api/pixel/v1/eventreturning204. - A
204with no row stored usually means an origin allowlist is configured under Workspace Settings and your domain isn't on it. - Verify Do Not Track / GPC isn't enabled in your test browser — the pixel intentionally goes silent when it is.
Conversions not attributed to a link
- Attribution is by UTM. Make sure the link a visitor arrives from carries
utm_source/utm_medium/utm_campaignthat match a link in your workspace. See UTM Parameters.
SPA route changes not tracked
- Ensure
data-spaisn't set to"false". The pixel relies on the History API; routers that do full page reloads will fire normalpage_views instead.
Next steps
- Web Analytics dashboard — read the data the pixel produces
- UTM Parameters — how link attribution works
- Mobile SDKs — the app-side counterpart to the pixel