@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 asEXPO_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_providertoo. - 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 answersexisting, the organization already has its app: writepublishableKey, 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.
__DEV__) shows as Local and a release build as Live, so testing on a simulator never marks Signal live.
Install
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 inHintoProvider 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 asbook/[id] or Settings/Profile, never its params:
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 exceptapiKey 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
audisHINTO_SIGNAL_APP_ID, the Signal app ID shown under Backend in Settings > Hinto Signal (applicationIdwhen you paired). See Issue a token on your authenticated server.
Support identity without a backend
When customers sign in with Supabase, Firebase, Clerk, Auth0, or another OpenID provider, name that provider once in Hinto: asidentity_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:
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.
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.
SupportProviderconnects once a customer is signed in, follows sign-outs and account switches without reconnecting, and destroys the runtime on unmount.useSupportreads its state, anduseSupportEntrygives the unread count for a “Chat with us” row.SupportConversationis 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, passtopSafeArea={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.headerActionsputs your controls at the end of its header, such as a close button for a modal.stylestyles its outer view, andtestIDnames it for your tests.pickAttachmentis 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 failedwith the error. WithoutpickAttachmentthere 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_TYPESandMAX_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.HelpOffershows 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, passvisiblefrom the list’s viewability callback.onContactSupportopens your support screen.useSupportConversationanduseHelpOffergive the same behavior as data, for a design you draw yourself, andcreateSupportControlleris the controller under them.SupportConversationViewdraws the stock conversation from the controlsuseSupportConversationreturns, so you can wrap an action before it runs. It takes the sameheaderActions,topSafeArea,styleandtestID, anddateFormat({ locale, timeZone }) for message times and day separators, whichSupportConversationreads fromSupportProvider. WithoutdateFormatit shows no times.selectViewandprojectViewturn a runtime state into one ofSUPPORT_VIEW_KINDS, with theREQUIRED_ACTIONSeach kind must offer.- Theme with
theme:colorSchemepickslightThemeordarkThemeas the base,colorsreplaces single colors, andfontsnames one font family per weight (regular,medium,semibold), asresolveThememerges 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 aMIN_TOUCH_TARGETof 44 points, and text grows with the system text size up to 1.6 times. Translate withcopy, which overridesdefaultCopykey by key, asresolveCopymerges it, using the web kit’s keys. A localized app passes every key, including the function keys such asopenAttachment(filename): each key it leaves out shows in English.
Limits
The support conversation shares the web’s limits, exported asMAX_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 withdebug: 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.