Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"sdk/ios",
"sdk/android",
"sdk/expo",
"sdk/web",
"sdk/unity",
"sdk/shopify"
]
Expand Down
12 changes: 11 additions & 1 deletion introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Linkrunner is a Mobile Measurement Partner (MMP) that helps founders, growth mar

## SDKs

Pick your platform — every SDK ships with attribution, deferred deep linking, event tracking, and revenue capture out of the box.
Pick your platform. Mobile SDKs cover app attribution, while the Web SDK measures website traffic and events.

<Tabs>
<Tab title="Mobile">
Expand Down Expand Up @@ -71,6 +71,16 @@ Pick your platform — every SDK ships with attribution, deferred deep linking,
</Card>
</CardGroup>
</Tab>
<Tab title="Web">
<CardGroup cols={2}>
<Card title="Web SDK" icon="globe" href="/sdk/web">
Beta website attribution for traffic, campaigns, users, and events
</Card>
<Card title="Shopify" icon="shopping-bag" href="/sdk/shopify">
Storefront and checkout attribution
</Card>
</CardGroup>
</Tab>
</Tabs>

## Ad Network Integrations
Expand Down
349 changes: 349 additions & 0 deletions sdk/web.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
---
title: "Web SDK"
description: "Set up Linkrunner's beta Web Attribution SDK for page views, users, events, and traffic-source attribution"
icon: "globe"
---

The Linkrunner Web SDK tracks page views, known users, custom events, and the traffic sources that brought visitors to your website.

<Warning>
Web Attribution is currently in beta. To request access, email
[support@linkrunner.io](mailto:support@linkrunner.io) with your project name and website domain.
We will enable Web Attribution and send you a Web SDK token.
</Warning>

The SDK automatically captures:

- Page views, including single-page app navigation
- First-touch and last-touch UTM attribution
- Ad click IDs such as `gclid`, `fbclid`, and `ttclid`
- Paid, organic, social, AI search, referral, and direct traffic
- Browser, device, geography, and performance data

## 1. Add the SDK

We recommend loading the browser SDK from the Linkrunner CDN. This lets Linkrunner ship fixes and updates without requiring you to change or redeploy your integration.

The direct script tag and the Next.js helper both load `https://cdn.linkrunner.io/web/v1/lr.js` by default. The [npm package](https://www.npmjs.com/package/@linkrunner/web) provides the typed Next.js component and event methods, while the browser SDK still stays current through the CDN.

<Tabs>
<Tab title="CDN script (preferred)">
Add this script before the closing `</head>` tag. Replace `YOUR_WEB_SDK_TOKEN` with the token provided by Linkrunner.

```html
<script
src="https://cdn.linkrunner.io/web/v1/lr.js"
data-token="YOUR_WEB_SDK_TOKEN"
defer
></script>
```
</Tab>

<Tab title="Next.js App Router">
Install the package:

```bash
npm install @linkrunner/web
```

Add `LinkrunnerScript` to your root layout so it loads once and stays active across navigation:

```tsx
// app/layout.tsx
import { LinkrunnerScript } from '@linkrunner/web/next'

export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<LinkrunnerScript token="YOUR_WEB_SDK_TOKEN" />
</body>
</html>
)
}
```

`LinkrunnerScript` loads the browser SDK from the Linkrunner CDN by default.
</Tab>

<Tab title="Next.js Pages Router">
Install the package:

```bash
npm install @linkrunner/web
```

Add `LinkrunnerScript` to `_app.tsx`, not `_document.tsx` or an individual page:

```tsx
// pages/_app.tsx
import { LinkrunnerScript } from '@linkrunner/web/next'

export default function App({ Component, pageProps }) {
return (
<>
<LinkrunnerScript token="YOUR_WEB_SDK_TOKEN" />
<Component {...pageProps} />
</>
)
}
```

`LinkrunnerScript` loads the browser SDK from the Linkrunner CDN by default.
</Tab>
</Tabs>

The SDK tracks the first page view when it loads. Single-page app navigation is tracked by default.

## 2. Identify users and track events

Call `identify` after a user signs in or when you otherwise know their identity. Use a stable internal user ID rather than an email address or phone number.

```js
import { lr } from '@linkrunner/web'

lr.identify(String(user.id))
```

With the script tag, use the global object:

```js
window.lr.identify(String(user.id))
```

The SDK saves this ID in `localStorage` and includes it as `user_id` on later events.

Track a custom event with `track`:

```js
lr.track('purchase', {
amount: 49.99,
currency: 'USD',
})
```

To show a signed-up user's details in the Web Events dashboard, identify the user and then send a `signup` event:

```js
lr.identify(String(user.id))

lr.track('signup', {
name: user.name,
email: user.email,
phone: user.phone,
})
```

Only send personal data when you have permission to do so. Calling `identify` does not add the user ID to events that were already captured.

<Note>
Calls made before the SDK finishes loading are queued and replayed after initialization.
</Note>

## 3. Verify the integration

Set `data-debug="true"` while testing:

```html
<script
src="https://cdn.linkrunner.io/web/v1/lr.js"
data-token="YOUR_WEB_SDK_TOKEN"
data-debug="true"
defer
></script>
```

Then open your browser's developer tools:

1. In **Console**, confirm that messages start with `[Linkrunner]` and include `Initialized`.
2. In **Network**, confirm that page views and events send a `POST` request to `/web/ingest`.
3. Trigger a test event and confirm the console reports `Sent via fetch`.

Debug logging turns on automatically on `localhost`, `127.0.0.1`, and `[::1]`. Remove `data-debug="true"` after testing.

## Configuration

### Script tag attributes

| Attribute | Required | Description | Default |
| --- | --- | --- | --- |
| `data-token` | Yes | Your Web SDK token | None |
| `data-domain` | No | Your first-party collection hostname, such as `lr.example.com` | None |
| `data-endpoint` | No | A full URL or same-origin path for a proxy you operate | None |
| `data-spa` | No | Set to `"false"` to disable automatic SPA page views | `true` |
| `data-debug` | No | Set to `"true"` or `"false"` to control console logging | On for local development |

You can also set the same options before the script loads:

```html
<script>
window.LinkrunnerConfig = {
token: 'YOUR_WEB_SDK_TOKEN',
spa: true,
debug: false,
}
</script>
<script src="https://cdn.linkrunner.io/web/v1/lr.js" defer></script>
```

Without `data-domain` or `data-endpoint`, events go to `https://api.linkrunner.io/web/ingest`.

## First-party collection

Some ad blockers stop requests to analytics domains. First-party collection sends events through your own domain instead.

### Proxy through your website

This is the most reliable option because both the SDK and event endpoint use paths on your website. For Next.js, add two rewrites:

```js
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/lr/lr.js',
destination: 'https://cdn.linkrunner.io/web/v1/lr.js',
},
{
source: '/lr/ingest',
destination: 'https://api.linkrunner.io/web/ingest',
},
]
},
}
```

Point `LinkrunnerScript` at those routes:

```tsx
<LinkrunnerScript
token="YOUR_WEB_SDK_TOKEN"
scriptSrc="/lr/lr.js"
endpoint="/lr/ingest"
/>
```

For a plain script tag:

```html
<script
src="/lr/lr.js"
data-token="YOUR_WEB_SDK_TOKEN"
data-endpoint="/lr/ingest"
defer
></script>
```

<Warning>
Your proxy must preserve the visitor's IP address. Forward `X-Forwarded-For` with the visitor's address first, or set `X-Linkrunner-Visitor-IP` explicitly. If the proxy drops it, geographic data will identify your proxy instead of the visitor.
</Warning>

### Point a subdomain at Linkrunner

Use this option when you cannot add proxy routes to your website.

<Steps>
<Step title="Register the subdomain">
In the Linkrunner dashboard, open **Settings → Manage Domains** and add the collection subdomain you want to use, such as `lr.example.com`.
</Step>

<Step title="Add the DNS record">
Add a CNAME record with your DNS provider:

```text
lr.example.com. CNAME api.linkrunner.io.
```

Linkrunner issues the TLS certificate on the first request for a registered subdomain.
</Step>

<Step title="Configure the SDK">
Add `data-domain` to the script tag:

```html
<script
src="https://cdn.linkrunner.io/web/v1/lr.js"
data-token="YOUR_WEB_SDK_TOKEN"
data-domain="lr.example.com"
defer
></script>
```

For Next.js, use the `domain` prop:

```tsx
<LinkrunnerScript token="YOUR_WEB_SDK_TOKEN" domain="lr.example.com" />
```
</Step>

<Step title="Verify the endpoint">
Run this request before relying on the subdomain:

```bash
curl -i -X OPTIONS \
-H 'Origin: https://example.com' \
-H 'Access-Control-Request-Method: POST' \
https://lr.example.com/web/ingest
```

Expect a `204` response with an `access-control-allow-origin` header. Then confirm in your browser's **Network** tab that event requests go to `https://lr.example.com/web/ingest`.
</Step>
</Steps>

<Note>
Set `data-domain` to a hostname, not a URL. The SDK accepts a scheme or trailing slash, but it always normalizes the value to `https://HOST/web/ingest`. Use `data-endpoint` only when you control the full proxy path.
</Note>

<Warning>
If each event creates one request to your subdomain and another to `api.linkrunner.io`, the first-party endpoint is failing and the SDK is using its fallback. Check the CNAME, domain registration, and any firewall or authentication rules in front of the subdomain.
</Warning>

## Attribution storage

| Data | Storage | Lifetime |
| --- | --- | --- |
| First-touch UTMs and click IDs | `localStorage` | Until browser storage is cleared |
| Last-touch click IDs | `localStorage` | 90 days |
| Last-touch UTMs | `sessionStorage` and `localStorage` | 24 hours from the campaign click |
| Visitor ID and user ID | `localStorage` | Until browser storage is cleared |
| Session ID and page count | `sessionStorage` | Current tab session |

The 24-hour `localStorage` copy preserves last-touch UTMs when a payment gateway or 3D Secure flow returns the visitor in a new tab.

## Troubleshooting

<AccordionGroup>
<Accordion title="The SDK says the token is invalid">
Confirm that you are using the Web SDK token provided by Linkrunner. Mobile SDK project tokens do not work with the Web SDK. If you need a token, contact [support@linkrunner.io](mailto:support@linkrunner.io).
</Accordion>

<Accordion title="Page views are duplicated">
Load the SDK once. In Next.js, put `LinkrunnerScript` in the root layout or `_app.tsx`, not on individual pages. The SDK already tracks SPA navigation by default.
</Accordion>

<Accordion title="Events are blocked in the browser">
Use [first-party collection](#first-party-collection). A same-origin proxy is the strongest option. A CNAME may still be detected by browsers that inspect DNS records.
</Accordion>

<Accordion title="A payment or other trusted event can be sent from the browser">
Do not trust client-side events for payments, entitlements, or other sensitive state changes. Send those events from your backend with the [Event Capture API](/api-reference/event-capture) or [Revenue Tracking API](/api-reference/revenue-tracking).
</Accordion>
</AccordionGroup>

## More resources

<CardGroup cols={2}>
<Card title="npm package" icon="npm" href="https://www.npmjs.com/package/@linkrunner/web">
View the package, current version, and full SDK reference.
</Card>
<Card title="GitHub repository" icon="github" href="https://github.com/linkrunner-labs/web-sdk">
Read the source code and release history.
</Card>
<Card title="Shopify setup" icon="shopping-bag" href="/sdk/shopify">
Install Web Attribution on a Shopify storefront and checkout.
</Card>
</CardGroup>

**Need help or beta access?** Contact [support@linkrunner.io](mailto:support@linkrunner.io)
Loading