Skip to main content

Web SDK Integration

The Joryio Web SDK enables you to track user behavior, send events, and manage user profiles on your website.

Installation

Via npm/yarn

npm install @joryio/web-sdk
# or
yarn add @joryio/web-sdk

Via CDN

You can load the SDK from Joryio's CDN using either a pinned version (recommended for production) or the latest channel (auto-upgrading, for dev / internal).

<!-- Pinned: production-safe, immutable cache. You upgrade by changing the URL. -->
<script src="https://cdn.joryio.com/sdk/web/1.0.0/joryio.min.js"></script>

<!-- Latest: auto-upgrades within ~5 minutes of every SDK release. -->
<script src="https://cdn.joryio.com/sdk/web/latest/joryio.min.js"></script>
When to use which
  • Pinned (/1.0.0/) - production sites where you want deterministic control over when the SDK upgrades. The bundle is served with a one-year immutable cache, so returning visitors fetch it once and then zero network on subsequent page loads.
  • Latest - internal dashboards, staging, and small customers who are happy to receive SDK updates automatically. Cached 5 minutes with a 1-hour stale-while-revalidate window so new versions propagate within minutes of a release.

Upgrading a pinned integration. When Joryio ships a new SDK version, bump the number in your snippet:

<!-- Before -->
<script src="https://cdn.joryio.com/sdk/web/1.0.0/joryio.min.js"></script>
<!-- After -->
<script src="https://cdn.joryio.com/sdk/web/1.1.0/joryio.min.js"></script>

The browser treats the new URL as a fresh file, caches it immutably, and runs the new SDK. Old cached entries stay resident until their TTL expires or the user clears cache.

Verifying the version you're running. Every SDK response includes two diagnostic headers:

HeaderExampleMeaning
X-SDK-Version-Served1.0.0The SDK version currently deployed on Joryio's side.
X-SDK-Version-Requested1.0.0The version the snippet URL asked for (only set on pinned paths).

Open DevTools → Network → find joryio.min.js → Response Headers. If Requested differs from Served, your HTML snippet is pointing at an older version URL than what's live - your visitors may get the newer bundle cached under the older URL.

Quick Start

1. Initialize the SDK

First, initialize the SDK with your SDK key. You can find your SDK key in the Joryio dashboard under Settings → Apps.

import JoryioSDK from '@joryio/web-sdk';

// Initialize the SDK
const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_your_sdk_key_here',
enableDebug: false, // Enable debug logging in development
});

When loading from the CDN instead of npm, the same class is available as window.Joryio:

const joryio = new Joryio({
sdkKey: 'jry_sdk_web_your_sdk_key_here',
});

2. Identify Users

Identify users when they sign up or log in:

// Identify a user
joryio.identify('user_123');

// Set profile attributes separately
joryio.setAttributes({
email: 'user@example.com',
firstName: 'John',
lastName: 'Doe',
plan: 'premium',
signupDate: '2024-01-15',
});

3. Track Events

Track user actions and behavior:

// Track a custom event
joryio.track('Product Viewed', {
product_id: 'prod_123',
product_name: 'Premium Plan',
price: 99.99,
currency: 'USD',
});

// Track page views
joryio.track('Page Viewed', {
page: '/pricing',
title: 'Pricing Page',
category: 'Marketing',
});

Configuration Options

OptionTypeDefaultDescription
sdkKeystringRequiredYour SDK key from the Joryio dashboard
apiEndpointstringhttps://api-eu1.joryio.comOverride the API base URL (defaults to https://api-eu1.joryio.com); only needed for testing or dedicated deployments.
enableDebugbooleanfalseEnable debug logging in console
batchFlushIntervalnumber5000How often to send batched events to server (milliseconds). Events are queued locally and sent every 5 seconds to reduce network requests.
batchSizenumber50Maximum events to queue before auto-sending. If 50 events accumulate before timer, they're sent immediately to prevent data loss.
sendImmediatelybooleanfalseSend each event immediately without batching (not recommended for production)
sessionTimeoutnumber1800000Session timeout in milliseconds (default: 30 minutes). New session starts after this period of inactivity.
trackSessionStartbooleantrueAutomatically track "Session Start" event when a new session begins.
trackPageViewsbooleanfalseAutomatically track "Page Viewed" on page load
captureUTMbooleantrueAutomatically capture UTM parameters from URL for campaign attribution
resetSessionOnNewCampaignbooleanfalseStart a new session when UTM parameters change (useful for campaign-level session analytics)
trackDevicePropertiesbooleantrueInclude device info on Session Start
persistQueuebooleantruePersist queued events to localStorage to survive page reloads
inApp.allowHtmlJsInAppMessagesbooleanfalseAllow HTML in-app messages. The server strips <script> and on* handlers, so author JavaScript does not run; the message still renders in a script-capable sandboxed iframe, so this is opt-in. Native messages display regardless. See In-App Messaging.

Event Batching & Flushing

Events are batched locally and sent to the server in groups to optimize network usage:

  • Automatic flush: Events sent every batchFlushInterval milliseconds (default 5s)
  • Size-based flush: Sent immediately when batchSize events accumulate (default 50)
  • Manual flush: Call joryio.flush() to send queued events immediately
  • On page unload: Events automatically flushed via sendBeacon when user leaves the page
  • Queue cap: The local queue holds at most 1,000 events; if the cap is exceeded (e.g., long offline periods), the oldest events are dropped with a console warning
// Send events immediately instead of batching
const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
sendImmediately: true // Send each event immediately
});

// Or configure batching behavior
const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
batchFlushInterval: 10000, // Flush every 10 seconds
batchSize: 20 // Or when 20 events accumulate
});

// Or manually flush at any time
joryio.track('Important Event', {...});
joryio.flush(); // Send now

Session Management

Sessions track continuous user activity and are managed automatically:

  • Session timeout: Default 30 minutes of inactivity (configurable via sessionTimeout)
  • New session starts when:
    1. User first loads the page
    2. Session timeout period passes without any events
    3. User calls joryio.reset() (e.g., logout)
    4. New campaign is detected (if resetSessionOnNewCampaign is enabled)

Configuring session timeout:

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
sessionTimeout: 3600000 // 1 hour in milliseconds
});

// Or shorter session timeout
const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
sessionTimeout: 600000 // 10 minutes
});
tip

Sessions are automatically managed based on user activity. Each event tracked resets the inactivity timer.

Session Start Tracking

By default, the SDK automatically tracks a "Session Start" event whenever a new session begins. This event:

  • Can be used as a trigger in campaigns and journey flows
  • Includes all session context (UTM parameters, referrer, landing page, and - when trackDeviceProperties is on - device data)
  • Is stored on the user's profile like any other event, so segments and analytics can count sessions per user

Example session start event:

// Automatically tracked when user visits your site
{
event: "Session Start",
properties: {
utm_source: "google", // If UTM parameters present
utm_medium: "cpc",
utm_campaign: "spring_sale",
referrer: "https://google.com",
landing_page: "https://example.com/..."
},
userId: "user_123", // If identified
anonymousId: "anon_456",
sessionId: "sess_789"
}

Automatic Session Data

The Web SDK enriches Session Start events with device and environment data:

  • $user_agent
  • $timezone
  • $screen_width / $screen_height
  • $viewport_width / $viewport_height
  • $language / $languages
  • $platform
  • $browser
  • $device_id
  • country (ISO-3166-1 alpha-2, derived from IP at session start)

Use cases:

  1. Welcome journeys: Use Session Start as a journey trigger to reach users when they arrive on your site.

  2. Session-based segments: Session Start events are stored per user, so event-based segment conditions can count them - for example, "performed Session Start at least 10 times" (power users) or "has not performed Session Start in the last 7 days" (re-engagement).

  3. Campaign attribution: Filter analytics on Session Start and group by utm_campaign to see which campaigns drive the most sessions.

Disabling session start tracking:

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
trackSessionStart: false // Disable automatic session start events
});

In-App Messaging

The SDK displays in-app messages for you. Eligible campaigns appear on their own and impressions, clicks and dismissals are tracked automatically.

Delivery tokens

When the backend serves an eligible campaign it also issues a short-lived signed delivery token for it. The SDK echoes that token back when it reports an impression, click or dismissal, and the server verifies the signature before recording anything.

You do not have to do anything - the SDK handles this for you. It is documented because it changes what happens to a client that does not send one:

POST /v1/in-app/track   (no deliveryToken)
{ "success": false, "error": "A delivery token is required" }

The token is what makes an impression trustworthy: without it, anyone holding the SDK key - which ships inside every app and page - could report impressions and clicks for a campaign that was never shown, and your reporting would count them.

If in-app impressions stop being recorded after an upgrade, check that the app is running a current build of the SDK rather than a copy vendored earlier. Sites loading the hosted bundle from /sdk/web/latest/joryio.min.js pick this up automatically.

Two kinds of message content

A campaign arrives as one of two content shapes, and the SDK renders each one differently:

ContentWhat it isHow it renders
NativeStructured data - headline, body, image, buttonsPlain DOM elements, inserted as text nodes. No iframe, no script execution.
HTMLAuthor-supplied markup, CSS and JavaScriptA sandboxed iframe on your page.

Allowing HTML messages

HTML messages are turned off by default. An HTML message executes author-supplied JavaScript on your site, so turning it on is a decision your own team makes - not something switched on from a marketing dashboard:

joryio.init({
sdkKey: 'jry_sdk_web_YOUR_KEY',
inApp: {
allowHtmlJsInAppMessages: true, // default: false
},
});

Leaving it off does not disable in-app messaging. Native messages still display, because they are data written into DOM text nodes with no interpreter involved. HTML campaigns are skipped and logged to the console, so a site that has not opted in shows no message rather than a broken one.

If your Content Security Policy forbids inline scripts or framed content, leave this off and author your campaigns as native messages.

Styling native messages from your own CSS

A native message is real DOM in your page, not an iframe, so you can style it like anything else you own. The renderer emits stable hooks:

.joryio-inapp-native                     /* the card */
.joryio-inapp-native h2 /* headline */
.joryio-inapp-native p /* body */
.joryio-inapp-native img /* image */
.joryio-inapp-native button.primary /* first button */
.joryio-inapp-native button.secondary /* the rest */
.joryio-inapp-close /* close affordance */
.joryio-inapp-backdrop /* dim layer */
.joryio-inapp-modal / -banner / -slideup / -fullscreen /* per type */

Prefer the variables to the selectors. Everything a campaign can set is read from a custom property, so setting one on :root gives you a house style that campaigns can still override for a specific message:

:root {
--joryio-inapp-bg: #0A1240;
--joryio-inapp-fg: #FFFFFF;
--joryio-inapp-primary: #00C8B7;
--joryio-inapp-primary-fg: #041028;
--joryio-inapp-radius: 18px;
--joryio-inapp-font: 'Inter', system-ui, sans-serif;
--joryio-inapp-size: 15px;
--joryio-inapp-align: start; /* start | center | end */
--joryio-inapp-title-weight: 700;
}

The precedence, highest first:

  1. what the campaign sets in Style (optional) - written inline on the card
  2. your --joryio-inapp-* values
  3. the SDK's own defaults, which are system colours

So a campaign that sets nothing inherits your house style, and one that sets a background wins for that message only. No !important anywhere.

If you do reach for the class selectors instead, note that the SDK's stylesheet is injected at display time and therefore lands after yours in document order, so it wins ties. Add specificity - .joryio-inapp .joryio-inapp-native {…}

  • rather than a bare single-class rule.

Direction is handled for you: the card carries dir="auto", so a Hebrew or Arabic message aligns right and puts its primary button on the right, inside an otherwise left-to-right page.

Message Types

  1. Modal - Center of screen with backdrop
  2. Banner - Top of the page
  3. Slide-Up - Small notification from the bottom
  4. Full-Screen - Takeover message
  5. Custom - Your page decides placement

Callbacks

joryio.init({
sdkKey: 'jry_sdk_web_YOUR_KEY',
inApp: {
onMessageDisplay: (message) => console.log('shown', message.id),
onMessageClick: (message, action) => console.log('clicked', action),
onMessageDismiss: (message) => console.log('dismissed', message.id),
},
});

API Reference

Initialize

const joryio = new JoryioSDK(config)

Initialize the SDK with your configuration. The constructor returns a singleton - constructing it a second time returns the existing instance.

Identify

joryio.identify(userId)

Associate a user ID with the current session.

Parameters:

  • userId (string): Unique identifier for the user

Example:

joryio.identify('user_123');
joryio.setAttributes({
email: 'user@example.com',
name: 'John Doe',
plan: 'premium',
});

Track

joryio.track(eventName, properties?)

Track a custom event with optional properties.

Parameters:

  • eventName (string): Name of the event
  • properties (object, optional): Event properties

Example:

joryio.track('Order Completed', {
order_id: 'order_789',
total: 149.99,
items: 3,
});
Tracking Page Views

Enable automatic page view tracking:

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
trackPageViews: true
});

Alias

joryio.alias(newUserId)

Alias an anonymous user to a known user ID (useful after signup).

Parameters:

  • newUserId (string): The new user ID to associate with

Example:

// Before signup (anonymous tracking)
joryio.track('Viewed Landing Page');

// After signup
joryio.alias('user_123');
joryio.identify('user_123');
joryio.setAttributes({ email: 'user@example.com' });

Add Alias

joryio.addAlias(aliasLabel, aliasName)

Add a labeled alias to the current identified user.

Example:

joryio.addAlias('crm', 'crm_98765');

Reset

joryio.reset()

Clear the current user session (useful on logout). Also clears all UTM data including first touch and last touch attribution.

Example:

// On user logout
function handleLogout() {
joryio.reset();
// ... other logout logic
}

Get Anonymous ID

joryio.getAnonymousId()

Return the current anonymous ID - the id Joryio assigns to every visitor before they are identified. It is generated on first initialization, persisted in localStorage, and stable across page loads for the life of the anonymous visitor (reset() regenerates it on logout). This is the exact anonymousId attached to every event track() sends, so use it to stitch a server-side event or a consent-log entry to the same profile.

Returns:

  • string - the anonymous ID (always present)
Read it after the SDK has loaded

With the async snippet, window.joryio is a command queue until the SDK finishes loading, and a queued call cannot return a value. Read the id on the resolved instance after load, or from inside ready() (below).

Example:

joryio.ready(function (sdk) {
const anonId = sdk.getAnonymousId();
// attach it to your own consent log / server event
fetch('/consent', { method: 'POST', body: JSON.stringify({ anonymousId: anonId }) });
});

Get User ID

joryio.getUserId()

Return the current identified user ID, or null if the visitor is still anonymous (i.e. identify() has not been called).

Returns:

  • string | null

Example:

joryio.ready(function (sdk) {
const userId = sdk.getUserId(); // null until you call joryio.identify(...)
});

Ready

joryio.ready(callback)

Run callback(sdk) once the SDK is loaded and initialized. This is the safe way to read a value (such as getAnonymousId() / getUserId()) that a queued snippet stub cannot return before load. callback receives the SDK instance; if the SDK is already loaded, it runs immediately.

Example:

joryio.ready(function (sdk) {
console.log('anon:', sdk.getAnonymousId(), 'user:', sdk.getUserId());
});

Get UTM Data

joryio.getUTMData()

Get current, first touch, and last touch UTM parameters.

Returns:

  • Object with current, firstTouch, and lastTouch UTM data

Example:

const utmData = joryio.getUTMData();
console.log(utmData.current?.utm_source); // "google"
console.log(utmData.firstTouch?.utm_campaign); // "awareness_campaign"
console.log(utmData.lastTouch?.utm_campaign); // "conversion_campaign"

Update UTM

joryio.updateUTM()

Manually update UTM parameters from the current URL. Useful for Single Page Applications that change URLs without page reload.

Example:

// React Router
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function App() {
const location = useLocation();

useEffect(() => {
joryio.updateUTM();
}, [location]);
}

// Vue Router
router.afterEach(() => {
joryio.updateUTM();
});

Array Attributes

joryio.addToArray(key, value)
joryio.removeFromArray(key, value)

Manipulate array attributes efficiently.

Parameters:

  • key (string): Attribute name
  • value (any): Value to add or remove

Example:

// Add tags to user
joryio.addToArray('tags', 'vip');
joryio.addToArray('tags', 'premium');
// Result: tags = ['vip', 'premium']

// Add duplicate (no-op, prevents duplicates)
joryio.addToArray('tags', 'vip');
// Result: tags = ['vip', 'premium'] (unchanged)

// Remove tag
joryio.removeFromArray('tags', 'vip');
// Result: tags = ['premium']

// Common use cases
joryio.addToArray('interests', 'technology');
joryio.addToArray('purchasedProducts', 'prod_123');
joryio.addToArray('featureFlags', 'beta-access');

Behavior:

  • addToArray() only adds value if it doesn't already exist (prevents duplicates)
  • addToArray() creates a new array if the attribute doesn't exist
  • removeFromArray() removes all instances of the value
  • Changes are synced to the backend automatically, for both identified and anonymous users

Automatic Session Data

The Web SDK automatically enriches Session Start events with device and environment data. These values are saved to the user profile and device records:

  • $user_agent
  • $timezone
  • $screen_width / $screen_height
  • $viewport_width / $viewport_height
  • $language / $languages
  • $platform
  • $browser
  • $device_id
  • country (ISO-3166-1 alpha-2, derived from IP at session start)

Event Properties

Standard Properties

Every stored event carries these properties - $device_id is attached by the SDK, and the rest are added by Joryio's ingestion pipeline when the event is received:

  • $device_id: Stable per-browser device ID (attached by the SDK)
  • $session_id: Current session ID
  • $anonymous_id: Anonymous user ID (before identification)
  • $app_id: Your app ID
  • $app_name: Your app name
  • $platform: Always 'web' for Web SDK
  • $is_identified: Whether the user is identified

The $ prefix is reserved - do not use it for your own properties.

Custom Properties

You can add any custom properties to your events:

joryio.track('Video Played', {
video_id: 'vid_123',
video_title: 'Product Demo',
duration: 120,
autoplay: false,
// Any other custom data
});

Best Practices

1. Initialize Early

Initialize the SDK as early as possible in your application:

// In your main app file
import JoryioSDK from '@joryio/web-sdk';

const joryio = new JoryioSDK({
sdkKey: process.env.JORYIO_SDK_KEY,
enableDebug: process.env.NODE_ENV === 'development',
});

2. Track Meaningful Events

Focus on tracking events that matter for your business:

// Good: Specific, actionable events
joryio.track('Trial Started', { plan: 'premium' });
joryio.track('Feature Used', { feature: 'export', format: 'csv' });

// Avoid: Overly generic events
joryio.track('Button Clicked'); // Too generic

3. Use Consistent Naming

Use a consistent naming convention for events and properties:

// Good: Clear, consistent naming
joryio.track('Subscription Upgraded', {
from_plan: 'basic',
to_plan: 'premium',
billing_cycle: 'monthly',
});

// Avoid: Inconsistent naming
joryio.track('upgraded_subscription', {
FromPlan: 'basic',
'to-plan': 'premium',
});

4. Handle User Lifecycle

Properly handle user identification and session management:

// On login
function handleLogin(userId, userInfo) {
joryio.identify(userId);
joryio.setAttributes({
email: userInfo.email,
name: userInfo.name,
});
}

// On logout
function handleLogout() {
joryio.reset();
}

// On signup
function handleSignup(userId, userInfo) {
joryio.alias(userId);
joryio.identify(userId);
joryio.setAttributes(userInfo);
}

Troubleshooting

Events Not Appearing

  1. Check your SDK key - Ensure it starts with jry_sdk_web_
  2. Check the console - Enable debug mode to see detailed logs
  3. Verify initialization - Make sure new JoryioSDK(config) runs before other methods are called

CORS Errors

The SDK tracking endpoints respond with Access-Control-Allow-Origin: *, so no domain whitelisting is needed. If you still see CORS errors, check that apiEndpoint points at the correct base URL (https://api-eu1.joryio.com) and that the request is not being blocked by a browser extension or a proxy that strips CORS headers.

Session Tracking Issues

The SDK uses localStorage for session persistence. Ensure:

  • Your site is served over HTTPS (required for secure contexts)
  • Users haven't disabled localStorage
  • You're not calling reset() unintentionally

Next Steps