helpr docs

Mobile SDKs

Add Helpr chat, real-time visitor context, and visual co-browse to mobile apps.

Overview

The Helpr React Native SDK mirrors the web widget protocol for native apps. It connects through the same widget init endpoint, subscribes to the same visitor WebSocket channel, sends the same identity/custom/event/tag/card context, and renders chat with the same team theme settings.

Use it for fully native screens, fully WebView-based apps, and mixed native/WebView apps. Native views are serialized from the React Native view tree; wrapped web content should use HelprWebView so the SDK can capture the DOM and keep page metadata current.

Platform Support

PlatformSDKStatus
React Native iOS / Android@helprso/react-nativeComplete SDK
Expo@helprso/react-nativeComplete SDK
Native iOS Swift / UIKit / SwiftUISwift package foundationVisitor protocol and chat foundation; native view capture next
Native Android Kotlin / JavaKotlin library foundationVisitor protocol and chat foundation; native view capture next
The React Native SDK is currently the complete production SDK. Pure native iOS and Android SDK foundations use the same widget protocol and WebSocket model; full native visual assist capture will land as the UIKit/View hierarchy serializers are completed.

Installation

npm install @helprso/react-native react-native-webview

react-native-webview is a peer dependency. If your app uses Expo and does not already have the peer packages, install them with Expo's version-aware installer:

npx expo install react-native-webview
The SDK has no Expo runtime dependency. E2E encryption uses an app-provided getRandomValues provider first, then globalThis.crypto.getRandomValues, then a hidden WebView backed by browser WebCrypto. The SDK never falls back to Math.random for encryption keys or IVs.

Quick Start

The SDK automatically discovers the React Native view tree on mount. For best performance, import the register entry before React loads — this enables commit-driven snapshots (faster screen updates during co-browse). Without it, co-browse still works using interval-based capture.

Standard Expo / bare React Native

// index.ts  (set "main": "index.ts" in package.json)
import '@helprso/react-native/register';
import { registerRootComponent } from 'expo';
import App from './App';

registerRootComponent(App);

Expo Router

Expo Router’s default entry loads React before your route files run. For commit-driven snapshots, create a custom entry file:

// index.js  (set "main": "index.js" in package.json)
import '@helprso/react-native/register';
import 'expo-router/entry';
If you place the register import in app/_layout.tsx instead, co-browse still works but snapshot updates are interval-based rather than commit-driven.

Then wrap your app once at the top level. Put HelprChatProvider inside HelprCobrowseProvider.

import React from 'react';
import { Platform } from 'react-native';
import * as Device from 'expo-device';
import {
  HelprCobrowseProvider,
  HelprChatProvider,
} from '@helprso/react-native';

const helprConfig = {
  apiBase: 'https://www.helpr.so',
  widgetKey: 'YOUR_WIDGET_KEY',
  appName: 'Acme Mobile',
  appVersion: '1.0.0',
  page: {
    title: 'Home',
    url: 'acme://home',
  },
  deviceInfo: {
    os: Platform.OS === 'ios' ? 'iOS' : 'Android',
    osVersion: String(Platform.Version || ''),
    brand: Device.brand || '',
    model: Device.modelName || '',
  },
};

export default function App() {
  return (
    <HelprCobrowseProvider config={helprConfig}>
      <HelprChatProvider>
        <RootNavigator />
      </HelprChatProvider>
    </HelprCobrowseProvider>
  );
}

The SDK initializes the visitor, opens the main visitor WebSocket, sends heartbeats, tracks app focus, and waits for chat or co-browse activity.

Provider Order

HelprCobrowseProvider owns the visitor session, WebSocket connection, identity store, and co-browse capture. HelprChatProvider consumes that session to render chat UI and notification state.

<HelprCobrowseProvider config={helprConfig}>
  <HelprChatProvider>
    {/* useCobrowse() and useHelprChat() are available here */}
    <AppScreens />
  </HelprChatProvider>
</HelprCobrowseProvider>
HelprChatView can be rendered with or without HelprChatProvider, but it always needs HelprCobrowseProvider above it.

HelprChatProvider

The chat provider renders the modal chat sheet, top notification banner, unread state, read receipts, and optional floating chat button.

notificationBar boolean true

Shows a temporary top banner for incoming agent messages while chat is closed.

notificationDurationMs number 4500

How long the top banner stays visible.

chatBubbleVisible boolean team setting

Native equivalent of hiding the web bubble. Both the dashboard setting and this prop must allow the bubble.

floatingChatButton boolean | 'auto' 'auto'

Controls whether the SDK may render its native floating chat entry point.

hasCustomChatTrigger boolean false

Set to true when your app renders its own chat button or badge.

<HelprChatProvider
  notificationBar
  notificationDurationMs={5000}
  chatBubbleVisible={false}
  hasCustomChatTrigger
>
  <RootNavigator />
</HelprChatProvider>

useHelprChat()

Use useHelprChat() to open, close, and inspect the native chat UI from any child component.

import {
  useHelprChat,
  chatBubbleIcon,
} from '@helprso/react-native';

function SupportButton() {
  const chat = useHelprChat();

  return (
    <Pressable onPress={chat.open}>
      <Image
        source={chatBubbleIcon}
        style={{ width: 22, height: 22, tintColor: chat.themeColor }}
      />
      {chat.unreadCount > 0 && (
        <Text>{chat.unreadCount > 9 ? '9+' : chat.unreadCount}</Text>
      )}
    </Pressable>
  );
}
open()() => void

Open the chat modal.

close()() => void

Close the chat modal.

toggle()() => void

Open if closed, close if open.

isOpenboolean

Whether the modal is currently open.

unreadCountnumber

Current unread agent message count.

themeColorstring

Team widget color returned by /api/widget/init.

bubblePositionbottom-right | bottom-left | top-right | top-left

Team bubble position returned by widget init.

bubbleVisibleboolean

Effective visibility after combining dashboard settings and SDK props.

HelprChatView

Use HelprChatView when chat should be embedded inside a support screen instead of opened as a modal.

import { HelprChatView } from '@helprso/react-native';

function SupportScreen({ navigation }) {
  return (
    <View style={{ flex: 1 }}>
      <HelprChatView
        style={{ flex: 1 }}
        onUnreadCount={(count) => console.log('unread', count)}
        onClose={() => navigation.goBack()}
      />
    </View>
  );
}
styleViewStyle

Style applied to the chat WebView container.

onUnreadCount(count: number) => void

Called when unread count changes.

onClose() => void

Called when the embedded widget requests hidden state.

Notifications & Unread State

Incoming agent messages are received on the main visitor WebSocket. The provider increments unread count, shows a top banner, and sends a chat_read receipt when the user opens chat.

  • If the floating bubble is visible, unread count is shown on the bubble.
  • If the bubble is hidden and no custom trigger is registered, the SDK only shows a floating chat button while unread messages are pending.
  • If hasCustomChatTrigger is true, the SDK does not render a floating entry point. Your trigger should read unreadCount from useHelprChat().
  • On app launch, unread state is seeded from widget init when an active chat can be resumed.

Custom Triggers

Use a custom trigger when the host app already has a support tab, header action, or profile-menu item for chat.

<HelprChatProvider hasCustomChatTrigger>
  <AppScreens />
</HelprChatProvider>
function HeaderHelpButton() {
  const chat = useHelprChat();

  return (
    <Pressable onPress={chat.open} accessibilityRole="button">
      <Text>Help</Text>
      {chat.unreadCount > 0 && <Text>{chat.unreadCount}</Text>}
    </Pressable>
  );
}
chatBubbleIcon is exported as a PNG asset with 1x, 2x, and 3x variants. No SVG dependency is required.

Native Co-browse Capture

When an agent starts visual assist, the SDK captures the React Native view hierarchy, layout, scroll state, taps, and screen changes, then streams a live representation of the app to Helpr. Pure native screens do not require HelprWebView.

The provider sends an initial screen snapshot, then batches incremental updates after relevant UI commits. Agents can see native screens and layout changes without video capture, screenshots, or per-screen setup.

Co-browse starts only while HelprCobrowseProvider is mounted and the visitor connection is active. Stop and cleanup are handled automatically when the session ends or the provider unmounts.

Security & Bandwidth

Mobile Visual Assist uses the same zero-knowledge security model as the Helpr web widget. All visual assist traffic runs over WebSocket Secure: the main visitor WSS channel handles presence, chat, and visitor context, while a dedicated visual assist WSS channel carries the co-browse stream.

Screen content is end-to-end encrypted between the visitor app and the agent before it reaches Helpr’s relay. The session uses per-session P-256 key exchange, HKDF-SHA-256 key derivation, and AES-256-GCM message encryption. Larger screen payloads are gzip-compressed before encryption.

Because Helpr streams structured screen updates instead of video frames or repeated screenshots, bandwidth stays much lower than pixel-streaming tools. Typical active visual assist sessions average roughly 10-50 KB/s after the initial snapshot, with short bursts during large screen transitions. Video or screenshot-based co-browse tools commonly use hundreds of KB/s to multiple MB/s for the same session.

Helpr does not require WebRTC, native screen recording permissions, screenshot capture, or video streaming for mobile visual assist.

HelprWebView

Use HelprWebView instead of react-native-webview’s raw WebView for any web content you want represented accurately in co-browse.

import { HelprWebView } from '@helprso/react-native';

function StorefrontScreen() {
  return (
    <HelprWebView
      source={{ uri: 'https://example.com/account' }}
      style={{ flex: 1 }}
      cobrowseId="account-webview"
    />
  );
}

HelprWebView forwards normal WebView props and adds DOM capture, safe-area CSS normalization, scroll geometry, and automatic page metadata updates from location.href and document.title.

cobrowseIdstring

Stable identifier for this WebView inside the co-browse stream. Generated automatically if omitted.

injectedJavaScriptstring

Your script runs before the SDK appends its DOM capture script.

injectedJavaScriptBeforeContentLoadedstring

Your early script runs after the SDK marks the page as a Helpr SDK WebView.

onMessageWebViewProps['onMessage']

Receives non-Helpr messages. SDK capture messages are consumed internally.

onLoadWebViewProps['onLoad']

Called after the SDK syncs URL/title from load state.

onNavigationStateChangeWebViewProps['onNavigationStateChange']

Called after the SDK syncs URL/title from navigation state.

Control Metadata

The mobile SDK preserves interaction metadata in the co-browse stream so remote-control features can target the same native elements the user can interact with. Visual replay still uses positioned DOM nodes to match React Native layout; controls are described with metadata instead of browser-default button/link markup.

For built-in React Native interactions, the SDK records the exact handler surface it sees on the React fiber tree. This includes onPress, onLongPress, onPressIn, onPressOut, responder handlers, touch handlers, and scroll handlers. Metadata is inherited from composite wrappers such as Pressable and TouchableOpacity onto the native host view, then serialized as role, data-helpr-interactive, data-helpr-interactions, data-helpr-component, and data-helpr-native-tag.

Set normal accessibility props whenever possible. accessibilityRole="button", accessibilityRole="link", labels, disabled state, and similar props make the replay clearer for agents and give the control layer better intent.

<Pressable
  accessibilityRole="button"
  accessibilityLabel="Apply discount code"
  onPress={applyDiscount}
>
  <Text>Apply</Text>
</Pressable>

For custom gesture systems or domain-specific controls, add non-executable action names with helprActions or helprControlActions. The SDK only transports the action names; it never serializes or runs your function bodies.

<MealCard
  meal={meal}
  onPress={() => openMeal(meal.id)}
  onSwipeLeft={() => skipMeal(meal.id)}
  helprControlActions={['openMeal', 'swipeLeft', 'pinchZoomImage']}
/>
Interaction metadata is advisory until remote control is enabled for a session and the visitor grants interaction consent. Sensitive or blocked components are still omitted before metadata leaves the device.

Masking & Blocking

Use capture controls to avoid exposing sensitive input values or entire components during co-browse.

const helprConfig = {
  apiBase: 'https://www.helpr.so',
  widgetKey: 'YOUR_WIDGET_KEY',
  maskProps: ['secureTextEntry'],
  blockComponents: ['PaymentCardForm', 'SSNInput'],
};
maskProps

Component prop names that should cause sensitive values to be masked during serialization.

blockComponents

Component display/native names that should be omitted from co-browse capture.

Page Metadata

Page metadata controls what agents see as the visitor’s current page or native screen. Web pages inside HelprWebView are detected automatically. Native screens should set metadata from your navigation layer.

The SDK automatically detects native screen transitions, but page metadata is also an explicit navigation hint. Calling setPage() on real route changes starts the mobile Visitor is navigating state immediately and uses the fastest page-change snapshot path, so the agent view switches cleanly to the new screen. Avoid calling it on every render or for transient UI such as bottom sheets unless that UI is a real page-level destination.

import { useHelprPage, useHelprRoutePage } from '@helprso/react-native';

function LoginScreen() {
  useHelprPage({
    title: 'Sign in',
    url: 'acme://login',
  });

  return <LoginForm />;
}

function ProductScreen({ route }) {
  useHelprRoutePage(route, { scheme: 'acme' }, [route.name]);
  return <ProductDetails />;
}

You can also call setPage() directly from useCobrowse(), for example in a central navigation ref.

const cobrowse = useCobrowse();

navigationRef.addListener('state', () => {
  const route = navigationRef.getCurrentRoute();
  cobrowse.setPage({
    title: route?.name || 'App',
    url: `acme://${route?.name || 'app'}`,
  });
});
React Native does not reliably expose production source filenames to a generic SDK. Prefer explicit page data or route names from your navigation library.

Identity

identify() sets the current visitor’s identity and merges with previously stored identity fields. The SDK follows the same reserved fields as the JavaScript widget: email, name, userId, userHash, and company.

import { useCobrowse } from '@helprso/react-native';

function AccountBootstrap({ user }) {
  const cobrowse = useCobrowse();

  useEffect(() => {
    cobrowse.identify({
      email: user.email,
      name: user.fullName,
      userId: user.id,
      userHash: user.helprHash,
      company: {
        name: user.company.name,
        plan: user.company.plan,
        mrr: user.company.mrr,
      },
    });
  }, [cobrowse, user]);

  return null;
}
Call identify() after login and before opening chat when possible. If identity changes from anonymous to a known email or userId, the SDK may remap the visitor session to match the existing contact.

Push Notifications

The SDK can register an Expo push token so Helpr can notify the visitor when an agent replies while the app is closed or offline. The host app owns native notification permissions and token creation.

import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';
import { useCobrowse } from '@helprso/react-native';

function PushBridge() {
  const cobrowse = useCobrowse();

  useEffect(() => {
    let mounted = true;

    (async () => {
      const { status } = await Notifications.requestPermissionsAsync();
      if (status !== 'granted') return;

      const token = (await Notifications.getExpoPushTokenAsync()).data;
      if (!mounted) return;

      await cobrowse.setPushToken({
        token,
        provider: 'expo',
        platform: Platform.OS === 'ios' ? 'ios' : 'android',
      });
    })();

    return () => { mounted = false; };
  }, [cobrowse]);

  return null;
}

You can also pass push in CobrowseConfig when the token is already available. Tokens are tied to the SDK visitor ID and, when identity is present, Helpr’s hashed userId / email identity keys.

Helpr sends visitor push notifications only for public agent, bot, or AI replies when the visitor WebSocket is not online. The push payload contains identifiers such as chatId and messageId; the app decides how to open the relevant screen.

Custom Data

setCustom() attaches arbitrary traits to the visitor. They appear in the agent sidebar and are included in visitor context.

const cobrowse = useCobrowse();

cobrowse.setCustom({
  subscription: 'enterprise',
  trialEnds: '2026-05-01',
  features: ['sso', 'api', 'webhooks'],
});

Initial identity, custom data, tags, and cards can also be passed through CobrowseConfig. Runtime calls merge into persisted SDK storage.

Events, Tags, Cards

The SDK sends the same visitor context primitives as the web widget.

const cobrowse = useCobrowse();

cobrowse.track('checkout_completed', {
  total: 99.0,
  items: 3,
});

await cobrowse.tag('vip');
await cobrowse.untag('trial');

cobrowse.setCards([
  {
    type: 'text',
    title: 'Subscription',
    body: 'Enterprise plan, renewal due May 31',
  },
]);
track(event, metadata?)

Queues a visitor event for the agent timeline. Event names are limited to 100 characters.

tag(tag)

Adds a lowercased visitor tag. Tags are deduplicated and limited to 50 characters.

untag(tag)

Removes an existing visitor tag.

setCards(cards)

Replaces visitor sidebar cards. Up to 10 cards and 64 KB serialized payload.

update()

Sends an immediate heartbeat with current visitor/page state.

Reset

Call reset() on logout to clear identity, custom data, tags, cards, queued events, and the stored visitor ID. The SDK reconnects as a fresh visitor.

async function handleLogout() {
  await cobrowse.reset();
  await auth.logout();
}

CobrowseConfig

apiBasestringRequired

Helpr API origin, normally https://www.helpr.so.

widgetKeystringRequired

Your team widget key.

visitorIdstringgenerated

Optional stable visitor ID. When omitted, the SDK persists one.

appNamestringMobile App

App name shown as browser/application context.

appVersionstring1.0.0

App version shown to agents.

page{ title?, url?, referrer? }helpr-rn://app

Initial native page metadata.

deviceInfoobjectauto where possible

OS, OS version, brand, model, device type, screen, language, and timezone.

identityRecord<string, unknown>{}

Initial identity traits.

customRecord<string, unknown>{}

Initial custom data.

tagsstring[][]

Initial visitor tags.

cardsunknown[][]

Initial agent sidebar cards.

groupstringnone

Group routing key, equivalent to the web widget group setting.

push{ token, provider?, platform? }none

Optional Expo push registration. You can also call setPushToken() after the app obtains a token.

storageHelprStorageAdapter | nullmemory

Optional persistence adapter. Pass AsyncStorage, SecureStore, or a compatible adapter to persist visitor identity across app restarts.

getRandomValues(bytes: Uint8Array) => Uint8Array | void | Promiseglobal/WebView crypto

Optional secure random provider. If omitted, the SDK uses global crypto when present and otherwise falls back to WebCrypto inside an internal hidden WebView.

onViewConsentRequestfunctionnone

Async hook for view consent.

onInteractConsentRequestfunctionnone

Async hook for remote interaction consent.

maskPropsstring[][]

Props to mask during native serialization.

blockComponentsstring[][]

Component names to omit from capture.

snapshotIntervalnumber500

Co-browse snapshot interval in milliseconds.

debugbooleanfalse

Logs capture and transport details to the native console.

Exports

HelprCobrowseProvider

Top-level provider for visitor connection and visual assist.

useCobrowse

Hook for identity, custom data, tags, cards, events, push registration, reset, update, and page metadata.

useHelprPage

Hook for explicit native page metadata.

useHelprRoutePage

Navigation-agnostic hook that derives page metadata from a route object or string.

pageFromRoute

Helper that converts a route object/string into { title, url }.

HelprChatProvider

Provider for modal chat, notifications, unread count, and optional floating button.

useHelprChat

Hook for opening chat and reading chat state.

HelprChatView

Embedded chat view.

HelprChat

Lower-level chat component.

HelprWebView

WebView wrapper with DOM co-browse and page metadata capture.

chatBubbleIcon

PNG chat icon asset.

SessionState

Visual assist session state enum.

CobrowseConfig PageMetadata HelprPushRegistration

TypeScript types.

Auto-Captured Data

FieldSource
url, title, referrerpage config, setPage(), route helpers, or HelprWebView auto-capture.
screenDimensions.get('screen'), unless deviceInfo.screen is provided.
languageIntl or navigator.language, unless deviceInfo.language is provided.
timezoneIntl.DateTimeFormat().resolvedOptions().timeZone, unless deviceInfo.timezone is provided.
deviceTypetablet when the shortest screen dimension is at least 600, otherwise smartphone.
focusedReact Native AppState.
returningPersistent last-seen timestamp with a 30-minute threshold.
widgetStateNative chat open/hidden state.
Interaction metadataaccessibilityRole, labels, disabled state, supported React Native interaction handlers, and optional helprActions / helprControlActions.

Widget Protocol Parity

The native SDK follows the web widget’s visitor flow:

  1. Calls /api/widget/init with key, vid, optional group, touch: 1, and platform: 'mobile-sdk'.
  2. Uses the returned visitor channel, token, widget color, bubble position, visibility, assist WebSocket, and resumable chat state.
  3. Subscribes to the main visitor WebSocket channel with the returned token.
  4. Publishes visitor_heartbeat with page, device, geo, identity, custom data, tags, and widget state.
  5. Publishes visitor_track, visitor_tags, visitor_cards, and chat read receipts as state changes.
  6. Listens for assist invites, interact invites, visitor remaps, chat activation, chat messages, proactive messages, and chat end events.
For apps that wrap the Helpr web app or another website, combine HelprWebView with identity/custom/page calls from the native shell so the native session stays aligned with the web widget model.

Full Example

import React, { useEffect } from 'react';
import { Image, Platform, Pressable, Text, View } from 'react-native';
import * as Device from 'expo-device';
import {
  HelprCobrowseProvider,
  HelprChatProvider,
  HelprWebView,
  chatBubbleIcon,
  useCobrowse,
  useHelprChat,
  useHelprRoutePage,
} from '@helprso/react-native';

const helprConfig = {
  apiBase: 'https://www.helpr.so',
  widgetKey: 'YOUR_WIDGET_KEY',
  appName: 'Acme Mobile',
  appVersion: '1.0.0',
  page: { title: 'Home', url: 'acme://home' },
  deviceInfo: {
    os: Platform.OS === 'ios' ? 'iOS' : 'Android',
    osVersion: String(Platform.Version || ''),
    brand: Device.brand || '',
    model: Device.modelName || '',
  },
  maskProps: ['secureTextEntry'],
};

export default function App() {
  return (
    <HelprCobrowseProvider config={helprConfig}>
      <HelprChatProvider hasCustomChatTrigger>
        <HomeScreen route={{ name: 'Home' }} />
      </HelprChatProvider>
    </HelprCobrowseProvider>
  );
}

function HomeScreen({ route }) {
  const cobrowse = useCobrowse();
  const chat = useHelprChat();

  useHelprRoutePage(route, { scheme: 'acme' }, [route.name]);

  useEffect(() => {
    cobrowse.identify({
      email: '[email protected]',
      name: 'Jane Smith',
      userId: 'usr_123',
    });
    cobrowse.setCustom({ plan: 'enterprise' });
    cobrowse.tag('mobile-user');
  }, [cobrowse]);

  return (
    <View style={{ flex: 1 }}>
      <HelprWebView
        source={{ uri: 'https://example.com/account' }}
        style={{ flex: 1 }}
      />
      <Pressable onPress={chat.open}>
        <Image
          source={chatBubbleIcon}
          style={{ width: 22, height: 22, tintColor: chat.themeColor }}
        />
        {chat.unreadCount > 0 && <Text>{chat.unreadCount}</Text>}
      </Pressable>
    </View>
  );
}