Skip to main content
@gethinto/react-native does for a mobile app what the browser SDK does for a web app. It records what the customer did (screens, taps, errors, console, network, leaving and returning to the app, identity and journeys), and it runs the in-app support conversation and Hinto Signal help offers. Both SDKs share one core, so a version behaves the same on the web and on a phone. The SDK is pure JavaScript. It runs in Expo Go, in development builds and in bare React Native apps, and an SDK fix reaches your users with your next over-the-air update. It needs React Native 0.82 or later with Hermes, or Expo SDK 55 or later.

What you need first

The publishable key of your organization’s Signal app. Your website and your iOS and Android apps share one Signal app and one publishable key. It is in Hinto under Settings > Hinto Signal, it can only send session data and open support, and it is safe to ship in the app. Store it as EXPO_PUBLIC_HINTO_PUBLISHABLE_KEY and pass it as apiKey.
  • Already have Signal on your website? Reuse the same publishable key and backend values. Your backend’s token endpoint already signs users, so the app calls that same endpoint. Do not pair and do not create anything in Hinto.
  • No backend, and customers sign in with Supabase, Firebase, Clerk, Auth0, or another OpenID provider? The app passes that provider’s token, as Support identity without a backend shows. When a coding agent pairs, it sends identity_provider too.
  • No Signal values anywhere you can read? A coding agent pairs as in Set up with a coding agent, steps 3 to 5, with "origins": []. If the claim answers existing, the organization already has its app: write publishableKey, and reuse the backend values its website’s backend already has, as step 4 says. Without an agent, an admin chooses Set up without a coding agent in Settings > Hinto Signal.
A mobile app needs no entry in the websites list: Hinto checks an origin only for requests from a browser. The app appears under Products in Settings > Hinto Signal after its first session. A debug build (__DEV__) shows as Local and a release build as Live, so testing on a simulator never marks Signal live.

Install

In a bare React Native app, use npm i @gethinto/react-native@^0.9.2 and the same peers, or react-native-device-info and react-native-get-random-values in place of the Expo modules. Each peer is optional, and the SDK works without it in a stated way: None of them adds native code that the SDK itself needs, so the SDK never ties a fix to a store release.

Start capture

Start capture once, as early as the app starts, and wrap the root in HintoProvider so Hinto can record taps:
initHinto resolves with the client once the SDK has read its stored state and any events the last run left unsent. A second call stops the first client. Off iOS and Android, such as when Expo renders your routes for the web or for static output, the client records and sends nothing, so a module-level call is safe there. HintoProvider renders one full-size View that watches touches without becoming a touch responder, so it never takes a touch from your components. It records a tap, not a scroll or a long press, with the tapped control’s label: hintoLabel first, then accessibilityLabel, then testID. It never records the text on screen. Put hintoIgnore on a view whose subtree Hinto must not label.
hintoLabel and hintoIgnore are props Hinto reads from the component tree. TypeScript may need them declared on your own components.

Follow screens

Pass the navigation container’s ref, and every event then carries the screen’s templated route, such as book/[id] or Settings/Profile, never its params:
With React Navigation, call trackNavigation(navigationRef) once the container is ready, with the ref from createNavigationContainerRef(). An app without either still records everything else, with no screen on its events.

Sessions on a phone

A session starts when the app starts cold, when the customer comes back after more than five minutes away, and after four hours or 10,000 events, so one session never spans days. Leaving and returning are recorded with the time away, so a case file reads “Left the app, came back after 7 minutes, then the error”. Events wait in a small queue on the device, capped at 512 KB, and go out every 5 seconds and when the app goes to the background. iOS suspends an app seconds after it leaves the screen, so what is not sent then goes out at the next start, under the session it belongs to. When Hinto refuses the publishable key, capture stops until the app starts again. Every request gives up after 30 seconds and is retried later.

Configuration

Every option except apiKey is optional. Uncaught errors are always recorded, chained to the handler that was already set, so React Native’s own error screen, Sentry and Bugsnag still see them. A release build’s stack is bytecode offsets until it meets the build’s source maps, so Hinto marks it as unsymbolicated. Run Sentry for readable native crashes and stacks: Hinto reads Sentry as evidence and lines its events up with the session by app version and time.

Client methods

The client has the same methods as the browser SDK’s: identify, journey, track, flush and shutdown. See Install the browser SDK. Call identify(null) at sign-out: it starts a new session and erases the queued events and journeys of the person who signed out, so they never attach to the next person on a shared device.

Support in the app

connectConversation from this package starts the support conversation, exactly as on the web, and rejects with a ConversationConnectError when Hinto cannot resolve the app. It returns a ConversationRuntime with the browser SDK’s methods; an attachment is a { uri, name, type, size } file from your picker. createConversation builds one from a config you already resolved. Start capture with initHinto first: the runtime runs on the same client. Pass the publishable key as ingestKey. The app sends no origin: the signed token is what Hinto checks. It comes from one of two places:
  • Your sign-in provider. Most apps without a backend. See Support identity without a backend.
  • Your backend. It signs the support token for the signed-in user, the same way it does for the web. The token’s aud is HINTO_SIGNAL_APP_ID, the Signal app ID shown under Backend in Settings > Hinto Signal (applicationId when you paired). See Issue a token on your authenticated server.
See security and identity for what Hinto checks.

Support identity without a backend

When customers sign in with Supabase, Firebase, Clerk, Auth0, or another OpenID provider, name that provider once in Hinto: as identity_provider when a coding agent pairs, or under Sign-in provider in Settings > Hinto Signal. The app then passes the provider’s own token, and needs no backend and no backend values. Use your sign-in provider’s token lists what to enter for each provider and what Hinto checks. With Supabase, enter {"kind":"supabase","url":"https://<ref>.supabase.co"}, the URL the app’s Supabase client uses. Pass the session’s user ID as subject, and its access token from getToken. Hinto calls getToken with true after a 401, so refresh the session then. Throw when nobody is signed in:
The Supabase project must sign with JWT signing keys, the default for new projects. A project on the legacy JWT secret must migrate in the Supabase dashboard under JWT Keys first. The other providers work the same way: Firebase passes user.uid and user.getIdToken(forceRefresh), Clerk passes userId and getToken({ skipCache: forceRefresh }) from useAuth(), and Auth0 passes user.sub and an access token requested for the API audience you entered. A phone is usually closed when a teammate replies. Set a reply webhook in Settings > Hinto Signal, under Reply notifications, and send a push from your backend when it fires. Without one, the Inbox tells your team the customer sees the reply the next time they open the app.

The support screen

@gethinto/react-native-support draws the conversation and help offers on this runtime, with the same states, wording and rules as the web kit. It needs a newer app than the SDK: React Native 0.86 or later, which is Expo SDK 57 or later. The SDK itself still runs on Expo SDK 55. Unlike the SDK, the support kit is not pure JavaScript: it needs native libraries for the keyboard, its animation, the safe area and its icons, at the versions Expo SDK 57 ships (react-native-keyboard-controller 1.21.9, react-native-reanimated 4.5, react-native-safe-area-context 5.7 and react-native-svg 15.15.4) or later. Every export of the kit loads all four, SupportProvider and HelpOffer included, so install all of them even when you use only part of the kit. Expo’s install command picks the versions that match your Expo SDK. If your app does not have them yet, make a new development build after you install them. The kit is not supported in Expo Go: use a development build.
Wrap your app’s root in KeyboardProvider from react-native-keyboard-controller, and in SafeAreaProvider from react-native-safe-area-context (Expo Router already adds it). Without KeyboardProvider the composer does not move with the keyboard, and react-native-keyboard-controller logs a warning in development.
  • SupportProvider connects once a customer is signed in, follows sign-outs and account switches without reconnecting, and destroys the runtime on unmount. useSupport reads its state, and useSupportEntry gives the unread count for a “Chat with us” row.
  • SupportConversation is the whole conversation: the status steps, every state’s notice and actions, the transcript and the composer. Give it the whole screen, and do not pad it for the status bar, the home indicator or the keyboard: its header keeps clear of the status bar and the Dynamic Island, and it keeps the composer above the home indicator and moves it with the keyboard itself. If your screen draws its own bar above the conversation, such as a navigation header, pass topSafeArea={false}. Show it on a screen without a tab bar, such as a stack screen or a modal above your tabs: inside a tab navigator the tab bar covers the bottom edge, and the composer would sit a tab bar’s height away from the keyboard. headerActions puts your controls at the end of its header, such as a close button for a modal. style styles its outer view, and testID names it for your tests. pickAttachment is your own picker, returning { uri, name, type, size } files, or null when the customer cancels. If the picker throws, the conversation tells the customer their files could not be opened and logs [Hinto] pickAttachment failed with the error. Without pickAttachment there is no attach button. Hinto accepts PNG, JPEG, GIF, WebP, HEIC and HEIF images, PDF, plain text, CSV and JSON, up to 10 MB each (ATTACHMENT_TYPES and MAX_ATTACHMENT_BYTES), and refuses other files before they upload. iPhone photos can go straight from the picker: Hinto stores a HEIC or HEIF photo as a JPEG named .jpg.
  • HelpOffer shows the Signal offer for one journey, with Contact support and Not now. It counts as shown only once it is on screen, which it measures itself (isOnScreen); inside a scrolling list, pass visible from the list’s viewability callback. onContactSupport opens your support screen.
  • useSupportConversation and useHelpOffer give the same behavior as data, for a design you draw yourself, and createSupportController is the controller under them. SupportConversationView draws the stock conversation from the controls useSupportConversation returns, so you can wrap an action before it runs. It takes the same headerActions, topSafeArea, style and testID, and dateFormat ({ locale, timeZone }) for message times and day separators, which SupportConversation reads from SupportProvider. Without dateFormat it shows no times. selectView and projectView turn a runtime state into one of SUPPORT_VIEW_KINDS, with the REQUIRED_ACTIONS each kind must offer.
  • Theme with theme: colorScheme picks lightTheme or darkTheme as the base, colors replaces single colors, and fonts names one font family per weight (regular, medium, semibold), as resolveTheme merges them. Name each weight’s font file as you loaded it: the kit then never sets a font weight on it. A weight you leave out uses the next lighter one. Without fonts the system font is used. Spacing, sizes and the type scale are fixed. Every control keeps a MIN_TOUCH_TARGET of 44 points, and text grows with the system text size up to 1.6 times. Translate with copy, which overrides defaultCopy key by key, as resolveCopy merges it, using the web kit’s keys. A localized app passes every key, including the function keys such as openAttachment(filename): each key it leaves out shows in English.

Limits

The support conversation shares the web’s limits, exported as MAX_MESSAGE_LENGTH, MAX_ATTACHMENTS_PER_MESSAGE, MAX_ATTACHMENT_BYTES, ATTACHMENT_TYPES (with attachmentTypeFor to type a file), MAX_HANDOFF_TURNS, MAX_HANDOFF_TURN_LENGTH, CONTACT_QUEUE_TTL_MS and SUPPORT_STATUS_PHASES. isSupportReply tells a teammate’s reply from the customer’s own messages.

Verify it works

Start the app with debug: true, sign in, and move between two screens. The Metro console shows [Hinto] ingest response: accepted N, duplicates N, rejected N. rejected 0 means Hinto took every event. In Hinto, Sessions shows the session with the phone’s model, OS and app version, and its screens. Remove debug: true afterwards.