Skip to main content

Installation

npm install @rlvt/web-sdk
The core SDK has zero dependencies and works with any runtime that supports the Web Fetch API (Node.js 18+, Bun, Deno, Cloudflare Workers).

Client setup

import { ReelevantClient } from '@rlvt/web-sdk'

const rlvt = new ReelevantClient({
  // timeout: 5000,        // default: 5000ms
  // fallback: 'empty',    // default: 'empty' — returns empty result on error
})
No configuration is required — the SDK defaults to the production runner.

Configuration options

OptionTypeDefaultDescription
runnerUrlstringhttps://reelevant.runRunner endpoint URL
timeoutnumber5000Global timeout in milliseconds
fallback'empty' | 'error' | Function'empty'What happens when a call fails

Fetching personalised content

Single workflow

import { extractUserId } from '@rlvt/web-sdk'

// Extract user identity from cookies
const userId = extractUserId(req.headers.cookie ?? '')

const result = await rlvt.run({
  workflowId: 'your-workflow-id',
  entrypoint: '43a490a0',
  userId,
  userAgent: req.headers['user-agent'],
  ip: req.headers['x-forwarded-for']?.split(',')[0],
  referer: req.url,
})

Multiple workflows in parallel

const [hero, sidebar, footer] = await rlvt.runAll([
  { workflowId: 'wf-hero', entrypoint: '43a490a0', userId },
  { workflowId: 'wf-sidebar', entrypoint: 'b7e21f3c', userId },
  { workflowId: 'wf-footer', entrypoint: 'd9c84e1a', userId },
])

Run options

OptionTypeDescription
workflowIdstringWorkflow ID from the Reelevant dashboard
entrypointstringEntrypoint shortId (8-char alphanumeric, e.g. 43a490a0)
userIdstring?Visitor identity from cookies
paramsRecord<string, string>?URL parameters forwarded to the runner
localestring?Locale for content resolution
userAgentstring?Forwarded for device detection
ipstring?Forwarded for geolocation
refererstring?Page URL
timeoutnumber?Per-call timeout override in ms

Handling the response

run() returns a RunResult with a discriminated union body:
const result = await rlvt.run({ workflowId: '...', entrypoint: '...' })

switch (result.body.type) {
  case 'html':
    // result.body.content is a string of HTML
    res.send(result.body.content)
    break

  case 'json':
    // result.body.content is a parsed JSON object
    res.json(result.body.content)
    break

  case 'image':
    // result.body.content is an ArrayBuffer
    res.setHeader('Content-Type', 'image/png')
    res.send(Buffer.from(result.body.content))
    break

  case 'empty':
    // No content returned — show your default
    res.send('<div>Default content</div>')
    break
}

RunResult fields

FieldTypeDescription
statusnumberHTTP status code (0 for fallback)
source'runner' | 'fallback'Where the result came from
bodyRunContentTyped content (see above)
metadataRecord<string, unknown>Metadata from the output node
propertiesRecord<string, unknown>Output properties
runIdstring | nullWorkflow run ID for tracking
executionPathstring[]Branch IDs taken during execution
redirectionUrlstringPre-built click-through URL (runner with mode=click)
trackClick() => Promise<void>Fire-and-forget callback to track the click server-side

Click tracking

Click tracking must always be set up after display. The flow is: fetch content → display it to the user → track clicks when they interact. Every content display should have a corresponding click tracking mechanism, otherwise you lose attribution data.
Every RunResult includes a redirectionUrl and a trackClick() callback. This ensures click tracking is always recorded — devs must use one of the two patterns below. Use redirectionUrl as the href on your call-to-action links. The browser handles everything — the runner tracks the click and 302-redirects to the final destination.
const result = await rlvt.run({ workflowId: 'wf-hero', entrypoint: '43a490a0', userId })

// In your template — always pair display with click tracking
// <a href="${result.redirectionUrl}">Shop now</a>

Option 2: Server-side fire-and-forget

Call result.trackClick() when you control navigation and want to track the click in the background. It calls the runner click endpoint with redirect: manual and swallows all errors — it never throws.
const result = await rlvt.run({ workflowId: 'wf-hero', entrypoint: '43a490a0', userId })

// User clicked a CTA (must be called after display) — track it server-side
await result.trackClick()
No arguments needed — the callback is pre-bound to the result’s redirectionUrl.

Identity helpers

extractUserId(cookies)

Extracts the Reelevant user ID from cookies. Priority: rlvt_clientId > rlvt_tmpId.
import { extractUserId } from '@rlvt/web-sdk'

// From a cookie header string
const userId = extractUserId(req.headers.cookie ?? '')

// From a parsed cookies object
const userId = extractUserId(req.cookies)

generateTmpId()

Generates a new anonymous temporary ID, matching the format used by the client-side tracker.
import { generateTmpId } from '@rlvt/web-sdk'

const tmpId = generateTmpId()
// Set as rlvt_tmpId cookie on the response

Fallback strategies

Configure how the SDK handles timeouts and errors:
// 'empty' (default) — returns an empty result, your page renders normally
const rlvt = new ReelevantClient({ fallback: 'empty' })

// 'error' — throws the original error, you handle it in your catch block
const rlvt = new ReelevantClient({ fallback: 'error' })

// Custom function — return your own fallback content
const rlvt = new ReelevantClient({
  fallback: (options, error) => ({
    status: 0,
    source: 'fallback',
    body: { type: 'html', content: '<div>Default banner</div>' },
    metadata: {},
    properties: {},
    runId: null,
    executionPath: [],
  }),
})

Request flow

Express example

import express from 'express'
import { ReelevantClient, extractUserId, generateTmpId } from '@rlvt/web-sdk'

const app = express()
const rlvt = new ReelevantClient()

app.get('/page', async (req, res) => {
  // Ensure identity cookie
  let userId = extractUserId(req.headers.cookie ?? '')
  if (!userId) {
    const tmpId = generateTmpId()
    res.cookie('rlvt_tmpId', tmpId, { maxAge: 365 * 24 * 60 * 60 * 1000 })
    userId = tmpId
  }

  const result = await rlvt.run({
    workflowId: 'wf-hero',
    entrypoint: '43a490a0',
    userId,
    userAgent: req.headers['user-agent'],
    ip: req.ip,
    referer: req.originalUrl,
  })

  const heroHtml = result.body.type === 'html' ? result.body.content : ''

  res.send(`
    <html>
      <body>
        <div data-rlvt-ssr="true">${heroHtml}</div>
      </body>
    </html>
  `)
})

Constants

The SDK exports commonly used constants:
import {
  DEFAULT_RUNNER_URL,   // 'https://reelevant.run'
  DEFAULT_TIMEOUT,      // 5000
  COOKIE_CLIENT_ID,     // 'rlvt_clientId'
  COOKIE_TMP_ID,        // 'rlvt_tmpId'
} from '@rlvt/web-sdk'