> Part of the walkerOS documentation. Project overview and full index: <https://www.walkeros.io/llms.txt>

[Source code](https://github.com/elbwalker/walkerOS/tree/main/packages/web/sources/session)[ Package](https://www.npmjs.com/package/@walkeros/web-source-session)

# Session source

Standalone session detection and management that can be composed with any walkerOS source.

## Installation[​](#installation "Direct link to Installation")

* Integrated
* Bundled

Install the packages:

```bash
npm install @walkeros/collector @walkeros/web-source-session
```

Configure in your code:

```typescript
import { startFlow } from '@walkeros/collector';
import { sourceBrowser } from '@walkeros/web-source-browser';
import { sourceSession } from '@walkeros/web-source-session';

const { collector, elb } = await startFlow({
  sources: {
    browser: { code: sourceBrowser },
    session: {
      code: sourceSession,
      config: {
        settings: {
          storage: true,
        },
      },
    },
  },
});
```

Add to your `flow.json` sources:

```json
"sources": {
  "browser": {
    "package": "@walkeros/web-source-browser"
  },
  "session": {
    "package": "@walkeros/web-source-session",
    "config": {
      "settings": {
        "storage": true
      }
    }
  }
}
```

[See Bundled Mode setup →](/preview/pr-749/docs/getting-started/modes/bundled.md) | [CLI reference →](/preview/pr-749/docs/apps/cli.md)

## Detection methods[​](#detection-methods "Direct link to Detection methods")

The session source uses two complementary methods:

| Method      | Storage      | Use Case                        |
| ----------- | ------------ | ------------------------------- |
| **Window**  | None         | Privacy-first, per-page session |
| **Storage** | localStorage | Cross-page session tracking     |

### Window-based detection[​](#window-based-detection "Direct link to Window-based detection")

Without storage, session detection relies on browser signals. A session starts on a page load that is a plain navigation, not a reload or back and forward, and that either carries marketing parameters or arrives from another host:

```typescript
// Simplified: when a page load starts a session
const [navigation] = performance.getEntriesByType('navigation');
const isNewSession =
  navigation?.type === 'navigate' &&            // a plain navigation
  (hasMarketingParameters(location.href) ||     // UTM parameters or a click ID
    referrerHostname !== location.hostname);    // another site or subdomain, or no referrer (direct)
```

Reloads, back and forward, internal links and single-page-app route changes start no session, and neither does a browser without Navigation Timing.

**Marketing parameters:**

UTM and other marketing parameters trigger session start, even with an internal referrer. The following are recognized by default:

```typescript
// UTM parameters → mapped to short names
utm_source    → source
utm_medium    → medium
utm_campaign  → campaign
utm_term      → term
utm_content   → content

// Click IDs → stored as clickId + original key
gclid, dclid, fbclid, msclkid, ttclid, twclid, igshid, sclid
```

To capture additional URL parameters, pass a `parameters` map. Each entry maps a URL parameter name to the property name it should be stored as in the session data:

```typescript
session: {
  code: sourceSession,
  config: {
    settings: { storage: true },
    parameters: {
      ref: 'referral',           // ?ref=newsletter → { referral: 'newsletter' }
      affiliate_id: 'affiliate', // ?affiliate_id=abc → { affiliate: 'abc' }
    },
  },
}
```

### Storage-based detection[​](#storage-based-detection "Direct link to Storage-based detection")

With `storage: true`, sessions persist across page loads:

```typescript
session: {
  code: sourceSession,
  config: {
    settings: {
      storage: true,
      length: 30, // Session timeout in minutes
    },
  },
}
```

**Session lifecycle:**

```
┌─────────────────────────────────────────────────────────────┐

│                    Session Timeline                         │

├─────────────────────────────────────────────────────────────┤

│                                                             │

│  User visits    Page 2       Page 3       30min idle       │

│       │            │            │              │            │

│       ▼            ▼            ▼              ▼            │

│   [Session 1] ─────────────────────────────► [Expires]     │

│   id: abc123                                                │

│                                                             │

│                                          User returns       │

│                                               │             │

│                                               ▼             │

│                                         [Session 2]        │

│                                         id: def456         │

└─────────────────────────────────────────────────────────────┘
```

**Storage settings:**

| Setting          | Default        | Description                                                                            |
| ---------------- | -------------- | -------------------------------------------------------------------------------------- |
| `sessionKey`     | `elbSessionId` | Storage key for the session ID                                                         |
| `deviceKey`      | `elbDeviceId`  | Storage key for the device ID                                                          |
| `sessionStorage` | `local`        | Storage type for the session ID (`local`, `session`, `cookie`)                         |
| `deviceStorage`  | `local`        | Storage type for the device ID (`local`, `session`, `cookie`)                          |
| `domain`         |                | Cookie domain, e.g. `example.com` to share IDs across subdomains (cookie storage only) |

## Session start event[​](#session-start-event "Direct link to Session start event")

When a new session is detected, the source pushes a `session start` event. It carries `source.type: 'session'` with the page `url` and `referrer`, so destinations see the landing URL with its campaign parameters. In storage mode the source also sets `user.session` and `user.device` on the collector, so every event of the session carries them:

```typescript
{
  name: 'session start',
  data: {
    isStart: true,           // Always true when this event fires
    isNew: true,             // True only on the device's very first session
    id: 'abc123',            // Session ID
    device: 'xyz789',        // Device ID (storage mode only)
    storage: true,           // Whether storage is active
    count: 5,                // Total session count for this device
    runs: 1,                 // Page views in this session
    start: 1704067200,       // Session start timestamp
    referrer: 'google.com',  // Hostname only (not full URL)
    marketing: true,         // Set when marketing parameters are present
    source: 'google',        // utm_source
    medium: 'cpc',           // utm_medium
    campaign: 'winter-sale', // utm_campaign
    clickId: 'gclid',        // Which click ID parameter matched (if present)
    platform: 'google',      // Ad platform of that click ID
    gclid: 'AW-123...',      // The actual click ID value
  },
  source: {
    type: 'session',
    platform: 'web',
    url: 'https://example.com/?utm_source=google&utm_medium=cpc',
    referrer: 'https://www.google.com/',
  }
}
```

**`isStart` vs `isNew`:**

| Field           | Meaning                                                          |
| --------------- | ---------------------------------------------------------------- |
| `isStart: true` | A new session began on this page load                            |
| `isNew: true`   | This is the device's very first session ever (storage mode only) |

A returning visitor starting their second session has `isStart: true` but `isNew: false`. `isNew` only appears in storage mode since window-only mode has no device memory.

The landing (navigation type, referrer, marketing parameters) is evaluated once per page load, so a route change in a single-page app that calls `walker run` does not start a new session, even when the new URL carries campaign parameters. Storage mode still counts each run in `runs` and starts a new session after inactivity.

**User IDs only with storage:** `user.session` and `user.device` are set only in storage mode, so they are present exactly when storage consent was given. In window mode the session ID would vanish on the next page load, so it is not a user ID and lives only in `data.id` of the `session start` event. Without storage consent, use a server-side key such as `user.hash` to group events.

**Click ID parameters** are stored twice: `clickId` identifies which platform originated the click (e.g. `'gclid'`), and a separate field stores the actual value (e.g. `gclid: 'AW-123...'`). This makes it easy to filter by platform without inspecting the value.

## Configuration[​](#configuration "Direct link to Configuration")

This source uses the standard source config wrapper (consent, data, env, id, ...). For the shared fields see [source configuration](/docs/sources#configuration). Package-specific fields live under `config.settings` and are listed below.

## Settings[​](#settings "Direct link to Settings")

| Property         | Type                               | Description                                                                                                                                                   | More |
| ---------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| `storage`        | `boolean`                          | Enable persistent storage for session/device IDs                                                                                                              |      |
| `consent`        | `string \| array`                  | Consent key(s) required to enable storage mode                                                                                                                |      |
| `length`         | `number`                           | Session timeout in minutes                                                                                                                                    |      |
| `pulse`          | `boolean`                          | Keep session alive on each event                                                                                                                              |      |
| `sessionKey`     | `string`                           | Storage key for session ID                                                                                                                                    |      |
| `sessionStorage` | `'local' \| 'session' \| 'cookie'` | Storage type for session                                                                                                                                      |      |
| `deviceKey`      | `string`                           | Storage key for device ID                                                                                                                                     |      |
| `deviceStorage`  | `'local' \| 'session' \| 'cookie'` | Storage type for device                                                                                                                                       |      |
| `deviceAge`      | `number`                           | Device ID age in days                                                                                                                                         |      |
| `domain`         | `string`                           | Cookie domain for session and device IDs, e.g. 'example.com' to share across subdomains (cookie storage only)                                                 |      |
| `cb`             | `function`                         | Custom session callback function or false to disable                                                                                                          |      |
| `clickIds`       | `Array<object>`                    | Custom click-ID registry. Entries with a \`param\` matching a default override the platform name in place; new params append to the end of the priority list. |      |

## Mapping[​](#mapping "Direct link to Mapping")

This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see [mapping](/docs/mapping).

## Examples

### New marketing session

A visit with UTM parameters starts a new session and emits walker user, session, and session start calls.

Event

```json
{
  "storage": true
}
```

Out

```javascript
elb("user", {
  "session": "s3ss10n-id",
  "device": "d3v1c3-id"
});

elb("session", {
  "id": "s3ss10n-id",
  "start": 1700000000000,
  "isNew": true,
  "count": 1,
  "runs": 1,
  "marketing": true,
  "source": "google",
  "medium": "cpc",
  "campaign": "winter-sale",
  "referrer": "",
  "device": "d3v1c3-id",
  "isStart": true,
  "storage": true,
  "updated": 1700000000000
});

elb({
  "name": "session start",
  "data": {
    "id": "s3ss10n-id",
    "start": 1700000000000,
    "isNew": true,
    "count": 1,
    "runs": 1,
    "marketing": true,
    "source": "google",
    "medium": "cpc",
    "campaign": "winter-sale",
    "referrer": "",
    "device": "d3v1c3-id",
    "isStart": true,
    "storage": true,
    "updated": 1700000000000
  },
  "source": {
    "type": "session",
    "platform": "web",
    "url": "https://example.com/?utm_source=google&utm_medium=cpc&utm_campaign=winter-sale",
    "referrer": ""
  }
})
```

### Returning visitor

A returning visit with a google referrer reuses the stored device id and increments the session count.

Event

```json
{
  "storage": true
}
```

Out

```javascript
elb("user", {
  "session": "n3w-s3ss10n",
  "device": "d3v1c3-id"
});

elb("session", {
  "id": "n3w-s3ss10n",
  "start": 1700001000000,
  "isNew": false,
  "count": 3,
  "runs": 1,
  "referrer": "google.com",
  "device": "d3v1c3-id",
  "isStart": true,
  "storage": true,
  "updated": 1700001000000
});

elb({
  "name": "session start",
  "data": {
    "id": "n3w-s3ss10n",
    "start": 1700001000000,
    "isNew": false,
    "count": 3,
    "runs": 1,
    "referrer": "google.com",
    "device": "d3v1c3-id",
    "isStart": true,
    "storage": true,
    "updated": 1700001000000
  },
  "source": {
    "type": "session",
    "platform": "web",
    "url": "https://example.com/",
    "referrer": "https://google.com"
  }
})
```

### Session without storage consent

Without storage consent the session runs in window mode: no user ids are set, the session id travels only as data.id of the session start event.

Event

```json
{}
```

Out

```javascript
elb("user", {});

elb("session", {
  "isStart": true,
  "storage": false,
  "start": 1700000000000,
  "id": "s3ss10n-id",
  "referrer": "",
  "marketing": true,
  "source": "google",
  "medium": "cpc",
  "campaign": "winter-sale"
});

elb({
  "name": "session start",
  "data": {
    "isStart": true,
    "storage": false,
    "start": 1700000000000,
    "id": "s3ss10n-id",
    "referrer": "",
    "marketing": true,
    "source": "google",
    "medium": "cpc",
    "campaign": "winter-sale"
  },
  "source": {
    "type": "session",
    "platform": "web",
    "url": "https://example.com/?utm_source=google&utm_medium=cpc&utm_campaign=winter-sale",
    "referrer": ""
  }
})
```

## Consent integration[​](#consent-integration "Direct link to Consent integration")

Session detection adapts to consent state:

| Consent State   | Behavior                           |
| --------------- | ---------------------------------- |
| No consent      | Window-only detection (no storage) |
| Consent granted | Storage-based with device ID       |
| Consent revoked | Falls back to window-only          |

```typescript
// Configure consent requirement
session: {
  code: sourceSession,
  config: {
    settings: {
      consent: 'analytics', // Wait for this consent key
      storage: true,
    },
  },
}

// Grant consent to enable storage
elb('walker consent', { analytics: true });
```

## Pulse mode[​](#pulse-mode "Direct link to Pulse mode")

With `pulse: true`, the source updates the session's last-seen timestamp on each page load without counting it as a new page view and without triggering a new `session start` event. Use this for heartbeat-style presence tracking where you want to extend session lifetime without inflating `runs`:

```typescript
session: {
  code: sourceSession,
  config: {
    settings: {
      storage: true,
      pulse: true,  // Extends session, does not increment runs
    },
  },
}
```

## Custom session callback[​](#custom-session-callback "Direct link to Custom session callback")

The `cb` setting lets you hook into session detection. It receives the detected session data, the source's collector interface, and the default callback. The interface carries only `push` (the source's pipeline) and `command`, with or without a consent gate, so collector state such as `consent` or `user` is not readable there:

```typescript
session: {
  code: sourceSession,
  config: {
    settings: {
      cb: (session, collector, defaultCb) => {
        // Run custom logic first
        console.log('New session:', session.id);

        // Then run the default behavior (sets user IDs + pushes event)
        defaultCb(session, collector);
      },
    },
  },
}
```

Set `cb: false` to completely disable the default behavior. No `user` commands and no `session start` event will be emitted. This is useful when you want full control over what happens after detection.

## Next steps[​](#next-steps "Direct link to Next steps")

* [Browser Source](/preview/pr-749/docs/sources/web/browser.md) - DOM-based event tracking
* [DataLayer Source](/preview/pr-749/docs/sources/web/dataLayer.md) - GTM/GA4 integration
* [Consent Guide](/preview/pr-749/docs/guides/consent.md) - Consent management patterns
