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>
- 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-revalidatewindow 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:
| Header | Example | Meaning |
|---|---|---|
X-SDK-Version-Served | 1.0.0 | The SDK version currently deployed on Joryio's side. |
X-SDK-Version-Requested | 1.0.0 | The 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
| Option | Type | Default | Description |
|---|---|---|---|
sdkKey | string | Required | Your SDK key from the Joryio dashboard |
apiEndpoint | string | https://api-eu1.joryio.com | Override the API base URL (defaults to https://api-eu1.joryio.com); only needed for testing or dedicated deployments. |
enableDebug | boolean | false | Enable debug logging in console |
batchFlushInterval | number | 5000 | How often to send batched events to server (milliseconds). Events are queued locally and sent every 5 seconds to reduce network requests. |
batchSize | number | 50 | Maximum events to queue before auto-sending. If 50 events accumulate before timer, they're sent immediately to prevent data loss. |
sendImmediately | boolean | false | Send each event immediately without batching (not recommended for production) |
sessionTimeout | number | 1800000 | Session timeout in milliseconds (default: 30 minutes). New session starts after this period of inactivity. |
trackSessionStart | boolean | true | Automatically track "Session Start" event when a new session begins. |
trackPageViews | boolean | false | Automatically track "Page Viewed" on page load |
captureUTM | boolean | true | Automatically capture UTM parameters from URL for campaign attribution |
resetSessionOnNewCampaign | boolean | false | Start a new session when UTM parameters change (useful for campaign-level session analytics) |
trackDeviceProperties | boolean | true | Include device info on Session Start |
persistQueue | boolean | true | Persist queued events to localStorage to survive page reloads |
inApp.allowHtmlJsInAppMessages | boolean | false | Allow 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
batchFlushIntervalmilliseconds (default 5s) - Size-based flush: Sent immediately when
batchSizeevents accumulate (default 50) - Manual flush: Call
joryio.flush()to send queued events immediately - On page unload: Events automatically flushed via
sendBeaconwhen 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:
- User first loads the page
- Session timeout period passes without any events
- User calls
joryio.reset()(e.g., logout) - New campaign is detected (if
resetSessionOnNewCampaignis 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
});
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
trackDevicePropertiesis 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_idcountry(ISO-3166-1 alpha-2, derived from IP at session start)
Use cases:
-
Welcome journeys: Use
Session Startas a journey trigger to reach users when they arrive on your site. -
Session-based segments: Session Start events are stored per user, so event-based segment conditions can count them - for example, "performed
Session Startat least 10 times" (power users) or "has not performedSession Startin the last 7 days" (re-engagement). -
Campaign attribution: Filter analytics on
Session Startand group byutm_campaignto 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:
| Content | What it is | How it renders |
|---|---|---|
| Native | Structured data - headline, body, image, buttons | Plain DOM elements, inserted as text nodes. No iframe, no script execution. |
| HTML | Author-supplied markup, CSS and JavaScript | A 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:
- what the campaign sets in Style (optional) - written inline on the card
- your
--joryio-inapp-*values - 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
- Modal - Center of screen with backdrop
- Banner - Top of the page
- Slide-Up - Small notification from the bottom
- Full-Screen - Takeover message
- 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 eventproperties(object, optional): Event properties
Example:
joryio.track('Order Completed', {
order_id: 'order_789',
total: 149.99,
items: 3,
});
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)
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, andlastTouchUTM 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 namevalue(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 existremoveFromArray()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_idcountry(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
- Check your SDK key - Ensure it starts with
jry_sdk_web_ - Check the console - Enable debug mode to see detailed logs
- 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
- Web SDK: UTM & Attribution - Campaign attribution, first/last touch, and UTM capture
- E-Commerce Tracking - Product, cart, checkout, and order events
- Track Custom Events
- Set User Attributes
- Create Segments
- Build Campaigns