> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gethinto.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hinto Signal

> Session capture and in-app support conversations for your React product.

Hinto Signal is the Hinto browser SDK plus the Native Support conversation. It records what each signed-in customer did, so tickets cite the visit, and it gives each customer a private conversation in your product. One [journey](/journeys), such as connecting an integration, tells each ticket how far the customer got in your most important flow. Hinto stores the transcript, connects the current browser session, and delivers your team's Inbox replies. Your product chooses the entry point, layout, authentication, and visual theme. The conversation supports React 19 on the web; session capture works in any web app.

## One app, every product

Your organization has one Signal app. Your website, your staging site, and your iOS and Android apps all use it, with the same keys. Never create a second app. Adding a product never needs a new key.

| What | Looks like | Where it goes | Where to find it |
| - | - | - | - |
| Publishable key | `hinto_pk_…` | Every website and mobile app. Pass it as `apiKey` to `initHinto` and as `ingestKey` to `connectConversation`. Safe to ship. | Settings > Hinto Signal. Always visible. |
| Backend values | `HINTO_SIGNAL_APP_ID`, `HINTO_SIGNAL_KID`, `HINTO_SIGNAL_SECRET` | Your backend only, when it signs tokens. The secret never reaches a browser or a phone. | Settings > Hinto Signal. The secret is shown once. |
| Secret key | `hinto_sk_…` | Your servers, scripts, and coding agents: the Hinto MCP, automations, and chat handoff from your server. Never in a website or app. | Settings > API keys. |

The support conversation needs a token that says who the signed-in customer is. It comes from one of two places:

* **Your sign-in provider.** When customers sign in with Supabase, Firebase, Clerk, Auth0, or another OpenID provider, name that provider once in Hinto, and the app passes the provider's own token. The app needs no backend and no backend values. See [Use your sign-in provider's token](#use-your-sign-in-providers-token).
* **Your backend.** It signs a token with the backend values. See [Issue a token on your authenticated server](#issue-a-token-on-your-authenticated-server).

Both work at the same time. A website can sign on its backend while a mobile app uses the provider's token.

Websites are the list in Settings > Hinto Signal: the exact origin of each website that runs Signal, up to 10. Hinto refuses support and session capture from a website that is not on the list. A mobile app needs no entry. It appears under Products after its first session. To add Signal to an iOS or Android app, follow [Install the React Native SDK](/react-native) with the same keys.

The signing secret is not a secret key. It only signs tokens on your backend, and it never goes in an `Authorization` header.

## What Signal shows your customers

Signal is flexible: your product decides where each piece appears, and a coding agent decides the placement with the person who asked for it (step 2 of the runbook below). This table is everything a customer can notice once Signal is installed.

| Piece | What customers see | When | Who changes it |
| - | - | - | - |
| Session recording | Nothing. Signal records pages, clicks, scrolls, console warnings and errors, script errors, and request addresses with their status, never what customers type. | From the first page a signed-in customer loads. | Your code ([Privacy controls](/privacy)). |
| Support entry and conversation | A Support control where your product puts it, and a private conversation with your team. | Signed-in customers only. Opening it creates nothing; sending the first message does. | Your code: the entry point, layout, and theme. |
| Unread count | The number of your team's replies they have not seen, for your entry control to show. | After your team replies while the conversation is closed. | Your code: whether the entry control shows `unreadCount`. |
| Automatic reply and status | An automatic reply after their first message, and a short status row: received, investigating, with a teammate, replied, resolved. | After they send. | Hinto: Settings > Auto-responses for the reply. Your code: `status={null}` on `Support.Messages` hides the status. |
| File uploads | An attach button, paste, and drag and drop in the composer. | Always, in the default composer. | Your code: a composer without `Support.Attach` has no uploads. |
| Help offers | A small card next to a step where they are stuck: Contact support, Try again, Not now. | Only where your code placed a slot, and only after Signal judges the customer stuck. | Your code places the slot. Hinto: Settings > Hinto Signal > Proactive help and Offer limits. |
| Journeys | Nothing. Your team sees how far the customer got in one flow and what failed. | On every ticket the customer sends during or soon after the flow. | Your code ([Journeys](/journeys)). |
| Handoff from your own assistant | Their conversation with your assistant continues with your team, in the same chat. | When the customer asks for a person, or your assistant hands over. | Your code ([Use your own chat](/native-support-own-chat)). |

## Set up with a coding agent

If you are a coding agent and a person asked you to add Hinto Signal to their app, this section is your runbook. Run every step yourself, in order, in one session. You need only HTTPS: no Hinto account, no secret key, and no MCP server.

The person does two things, and nothing else:

1. Answers your product questions, or says "defaults" (step 2).
2. Types your pairing code in Hinto and approves it (step 4).

Everything else is your job: pairing, credentials, environment files, deploy variables, code, and checks. Never ask the person to "pair", open Hinto settings, add websites or origins, copy IDs or keys, or start a server for you. If a step fails, fix it or retry it yourself.

### 1. Read the repository

Find each of these in the code. Do not ask the person for them.

* **The Hinto API.** Set `HINTO_API_URL` in your shell to `https://api.gethinto.com`. Use another Hinto API only if the prompt that sent you here names one. Do not take the API from Hinto URLs already in the repository or its env files, such as `NEXT_PUBLIC_HINTO_INGEST_URL` or a `dev-api` address. They come from older setups. Leave Hinto keys already in env files alone until step 4 tells you whether to replace them.
* **Signal values already in use.** Look in this repository's env files, and in the backend that signs tokens for the organization's website when you can read it, for `NEXT_PUBLIC_HINTO_PUBLISHABLE_KEY`, `VITE_HINTO_PUBLISHABLE_KEY`, `EXPO_PUBLIC_HINTO_PUBLISHABLE_KEY`, older `*_HINTO_INGEST_KEY` names, and `HINTO_SIGNAL_APP_ID`, `HINTO_SIGNAL_KID`, and `HINTO_SIGNAL_SECRET`. Step 4 tells you whether to reuse them.
* **An existing chat.** A component that renders a message list and a composer, an `/api/chat` route, or an import of `ai`, `@ai-sdk/*`, `openai`, `@anthropic-ai/sdk`, or `langchain`. Note the file where the chat lives.
* **An existing support widget.** A third-party script or package such as Intercom, Crisp, Zendesk, or HubSpot chat. It is not a chat you plug Hinto into. Note it for step 2.
* **React version.** With an existing chat in any framework, plug Hinto into it. With React 19 and no chat, install the full conversation. Otherwise install session capture only, as in [Browser SDK](/browser-sdk).
* **Development origins.** Read the dev script and config for the port: `next dev -p 3003` means `http://localhost:3003`. Next.js defaults to port 3000 and Vite to 5173.
* **Production origin.** Look in `vercel.json`, `netlify.toml`, `fly.toml`, a `CNAME` file, `metadataBase`, site URL variables, and the README.
* **Sign-in.** How the server reads the signed-in user, that user's stable ID, and where sign-out happens. Also find where the server reads the user's name and email, and their company, organization, or workspace name when the app has one.
* **Sign-in provider.** Whether customers sign in with Supabase, Firebase, Clerk, Auth0, or another OpenID provider, and the value Hinto needs from the app's config for it, as [Use your sign-in provider's token](#use-your-sign-in-providers-token) lists: the Supabase project URL, the Firebase project ID, the Clerk Frontend API URL, or the Auth0 domain and API audience. Also note whether the app has a backend that can sign tokens. A React Native app on Supabase, or a single-page app on Firebase, often has none.
* **The entry point.** The signed-in UI where support fits: an account menu, a help menu, the header, or a sidebar. Note any existing Sheet or Dialog component.
* **The critical journey.** One signed-in flow with an explicit success and failure, such as connecting an integration, checkout, submitting onboarding, or inviting a teammate. Prefer an integration connection when the product has one. Find five places in its code: the entry point where the flow begins, the submit handler, the existing error branch, the point where the server confirms success, and the component that shows the failure to the customer. The help offer goes in that component.
* **Deploy platform.** A linked project (`.vercel/project.json`, `.netlify/state.json`, `fly.toml`) and whether its CLI is installed and signed in (`vercel whoami`, `netlify status`, `fly auth whoami`).

An origin is exact: scheme, host, and port, with no path and no trailing slash. `http://localhost:3003` and `http://127.0.0.1:3003` are different origins. A mobile app has no origin.

### 2. Agree on what customers will see

Hinto Signal puts the pieces in [What Signal shows your customers](#what-signal-shows-your-customers) into the person's product. You choose where each piece goes. The person decides what their customers should see. For each topic below, pick a default from what you found in step 1.

* **Ask** topics change what customers see in a way the person cares about. Ask them before you build, unless the code already answers them.
* **Decide** topics have a safe default. Do not ask. Use the default and list it under "Decisions I made" in step 10, so the person can change it later.

| Topic | Ask or decide | The question, in plain words | Your default |
| - | - | - | - |
| Where support opens | Ask | Where should customers open support? I'll add a **Support** item to the account menu that opens a side panel. | The account menu, or the help menu or header if there is none. |
| An existing chat | Ask, when step 1 found one | Your app already has a chat assistant in `<path>`. Should support live inside it, so the assistant hands a conversation to your team when it can't help? I'll default to yes. | Yes. The other option is a separate Support panel. |
| An existing widget | Ask, when step 1 found one | Your app already loads `<widget>`. Should Hinto Signal replace it, or run beside it until you switch? | Run beside it. Remove nothing. |
| Help offers | Ask, when you report a journey | When a customer is stuck in that flow, should a small card appear next to the step, offering to try again or contact support? You can turn it off for each flow in Hinto. | Yes, next to the step that fails. |
| The journey | Decide, unless two flows are equally likely | Which flow hurts most when it fails? I'll report it so each ticket shows how far the customer got. | The integration connection, when the app has one. |
| How customers sign in | Decide, unless step 1 found neither | How do your customers sign in to this app? Hinto will trust that sign-in to know who is writing. | The app's sign-in provider when it is one Hinto accepts. Otherwise a token signed on the backend. |
| Who gets support | Decide | Should every signed-in user get support? | Every signed-in user. |
| Automatic reply and status | Decide | After a customer writes, they see an automatic reply and a short status: received, investigating, with a teammate, replied. Keep both? | Keep both. |
| File uploads | Decide | Customers can attach files and screenshots to a message. Keep that? | Keep uploads. |
| Session recording | Decide, and always tell | Signal records what signed-in customers do in your app: pages, clicks, errors, and request addresses, never what they type. Does your privacy policy cover that? | Record. Tell the person in step 10. |
| The assistant's messages | Decide, on the existing chat path | When your assistant hands a customer to your team, should your team see what the assistant said before the handoff? | Share them. |

Ask every **Ask** question that applies in one short message, each with your default, and nothing about keys, origins, environments, or Hinto settings. For example:

> Before I start: I'll add a **Support** item to your account menu that opens a side panel. When a customer gets stuck connecting Stripe, I'll also show a small help card next to the Connect button, offering to try again or contact support. Say "defaults", or tell me what to change.

Use the defaults when the person answers "defaults", does not care, or is not there to answer, for example when Hinto woke you without a person in the session. Do not wait for an answer that cannot come.

### 3. Start pairing

Send your own name as `client_name` (at most 80 characters) and every origin from step 1: the development origins and the production origin, 0 to 10 in total, each once. A mobile-only app sends `"origins": []`. If the prompt that sent you here included a `wake_token`, send it exactly as you received it.

When step 2 chose the sign-in provider, also send `identity_provider`, with the value for that provider from [Use your sign-in provider's token](#use-your-sign-in-providers-token), for example `"identity_provider":{"kind":"supabase","url":"https://abcd.supabase.co"}`. The person sees it when they approve. Leave it out when the backend signs tokens.

```bash theme={null}
curl -sS -X POST "$HINTO_API_URL/v1/signal/pair" \
  -H "Content-Type: application/json" \
  -d '{"client_name":"Claude Code","origins":["http://localhost:3000","https://app.example.com"]}'
```

A `201` response has these fields:

| Field | Meaning |
| - | - |
| `device_code` | Your secret for polling. Do not show it to the person or write it to disk. |
| `user_code` | The code the person types into Hinto, such as `WDJB-MJHT`. |
| `verification_uri` | The Hinto page where the person types the code. |
| `interval` | Seconds between polls. It is 5. |
| `expires_in` | Seconds until the pairing expires. It is 600. |

A `400` names the problem in `error`: `invalid_origin` or `duplicate_origin` (the values are in `invalid_origins`), `too_many_origins`, `invalid_client_name`, `invalid_identity_provider` (`error_description` says which value to use), or `invalid_request`. Fix the request and send it again. `invalid_wake_token` means the wake token is unknown, expired, or used; send the request again without it. A `429`, or a `503` with `pairing_capacity`, means wait for the `Retry-After` seconds and send it again. A `503` with `pairing_unavailable` means this Hinto API cannot pair; tell the person that Hinto could not start setup and stop.

### 4. Show the code, then poll until approved

Write the person one short message with `verification_uri` and `user_code`, and nothing else to do. Put it in your reply text, not only in command output, because the person does not read command output:

> Open [https://app.gethinto.com/pair](https://app.gethinto.com/pair) and type **WDJB-MJHT** to approve this app. I'll keep going when you do.

Do not explain pairing, keys, or origins. Do not add the code to the link: Hinto never reads the code from a URL, so the person types it. Do not end your turn: keep polling in the same session until you get an answer, so the person does not have to tell you they approved. If your shell stops commands after a few minutes, poll in a loop that runs in the background, or poll again with a new command. If you poll in the background, wait for that loop to finish before you end your turn. The pairing lasts 10 minutes.

Poll every `interval` seconds with curl:

```bash theme={null}
curl -sS -X POST "$HINTO_API_URL/v1/signal/pair/token" \
  -H "Content-Type: application/json" \
  -d "{\"device_code\":\"$DEVICE_CODE\"}"
```

| Status | `error` | What to do |
| - | - | - |
| 200 | | Approved. Go to step 5. |
| 400 | `authorization_pending` | Not approved yet. Poll again after `interval` seconds. |
| 400 | `slow_down` | Add 5 seconds to your interval and poll again. |
| 400 | `access_denied` | The person denied it. Stop, and ask them whether to try again. |
| 400 | `expired_token` | Ten minutes passed. Go back to step 3 and show the new code. |
| 400 | `invalid_grant` | The device code is unknown or already claimed. Go back to step 3. |
| 400 | `invalid_origin` | Go back to step 3 with exact origins. |
| 400 | `duplicate_origin` | Go back to step 3 and list each origin once. |
| 400 | `invalid_request` | The body has no `device_code`. Fix the request. |
| 400 | `too_many_origins` | The websites list would pass 10. Tell the person that an admin must remove unused websites in Settings > Hinto Signal, then poll again. |
| 400 | `multiple_applications` | The organization still has extra Signal apps from before Hinto allowed only one. Tell the person that an admin must pause the extra apps listed at the top of Settings > Hinto Signal, then poll again. |
| 400 | `invalid_identity_provider` | Hinto could not read the provider's keys from the issuer you sent. Go back to step 3 with the issuer exactly as the app's tokens name it in `iss`. |
| 400 | `identity_provider_conflict` | The organization's Signal app already trusts a different sign-in provider. Tell the person that an admin must change it under Sign-in provider in Settings > Hinto Signal, then poll again. Or go back to step 3 without `identity_provider` and sign tokens on a backend. |
| 503 | `setup_unavailable` | Poll again. |
| 5xx | | Poll again after `interval` seconds. If the next poll answers `invalid_grant`, go back to step 3. |
| 429 | | Wait for the `Retry-After` seconds, then poll again. |

Hinto sends the `200` response once. Keep it out of your messages: it can hold the signing secret. Read the values from it and write them in step 5.

| Field | Meaning |
| - | - |
| `status` | `created` for a new Signal app. `existing` when the organization already had one. |
| `publishableKey` | The publishable key, for every website and mobile app. Present when `status` is `created` and when it is `existing`. |
| `credentials.signingSecret` | The signing secret, for your backend only. `credentials` is `null` when `status` is `existing`. |
| `credentials.ingestKey` | The same key as `publishableKey`, under its older name. Read `publishableKey`. |
| `applicationId` | The Signal app ID. It goes in `HINTO_SIGNAL_APP_ID`, and your backend signs it into each token as `aud`. |
| `kid` | The signing key ID. It goes in `HINTO_SIGNAL_KID`, and your backend puts it in each token's header. |
| `identityProvider` | The sign-in provider whose tokens Hinto accepts, in the shape you sent, or `null` when the app signs its own tokens. |
| `stageToken` | One use, 30 minutes: what the stage command signs the staged session with (step 9). |
| `apiUrl` | The Hinto API this app talks to. |
| `origins` | Every website on the organization's list. Pairing added yours. |
| `docsUrl` | This page. |
| `notice` | A short reminder of where the values go, or why `credentials` is `null`. |

`notice` only repeats guidance on this page: where each value goes (step 5) and what to do when `status` is `existing` (below). If you lose the response, you lose only the signing secret. Pair again to get `publishableKey`, `applicationId`, and `kid` back.

Pairing adds your origins to the organization's websites list. There is nothing else to register.

If `status` is `existing`, the organization already has its Signal app, and every website and mobile app shares its keys. `credentials` is `null`, because Hinto shows the signing secret only once. Do this:

1. Write `publishableKey` as the publishable key in step 5. It is the same key the organization's other products use.
2. Reuse the backend values the organization already has: `HINTO_SIGNAL_APP_ID`, `HINTO_SIGNAL_KID`, and `HINTO_SIGNAL_SECRET`. Look in this repository's env files first, then in the env of the backend that already signs tokens for the organization's website, when you can read it. Keep the three values together, as that backend uses them. `HINTO_SIGNAL_APP_ID` must equal `applicationId`.
3. If this app talks to that same backend, and the backend already has a token endpoint, use that endpoint in step 7. This app then needs no backend values of its own.
4. Skip step 6 for values production already has.

On the sign-in provider path, skip items 2 and 3: the app needs no backend values. Check that `identityProvider` names the provider you sent.

If `publishableKey` is `null`, the app's key was made before Hinto stored keys. Keep the publishable key already in the env files. An older key that starts with `hinto_live_` still works.

If you cannot find the backend values and the app has no sign-in provider Hinto accepts, tell the person in one sentence, and never ask them to paste the signing secret into the chat: "Hinto already has a Signal app for your team, and it shows the signing secret only once. Put the team's `HINTO_SIGNAL_SECRET` and `HINTO_SIGNAL_KID` in `.env.local` and tell me, or ask a Hinto admin to choose **Roll secret** in Settings > Hinto Signal, which means updating every backend that signs Hinto tokens within 12 hours."

### 5. Write the local environment

Write the values to the project's local env file, `.env.local` for Next.js and Vite. Check that git ignores it (`git check-ignore .env.local`). Never write them to a committed file, and never print the signing secret.

| Variable | Value |
| - | - |
| `NEXT_PUBLIC_HINTO_PUBLISHABLE_KEY` or `VITE_HINTO_PUBLISHABLE_KEY` | `publishableKey` |
| `HINTO_SIGNAL_APP_ID` | `applicationId` |
| `HINTO_SIGNAL_KID` | `kid` |
| `HINTO_SIGNAL_SECRET` | `credentials.signingSecret` |
| `NEXT_PUBLIC_HINTO_API_URL` or `VITE_HINTO_API_URL` | `apiUrl`, only when it is not `https://api.gethinto.com` |
| `HINTO_STAGE_TOKEN` | `stageToken`, for step 9. Not a deploy variable. |

The browser reads only the publishable key and the API URL. The `HINTO_SIGNAL_` variables are read only by the server. On the sign-in provider path, the app has no server for them: do not write the `HINTO_SIGNAL_` variables. If a backend needs them later, an admin chooses **Roll secret** in Settings > Hinto Signal. An Expo app names the publishable key `EXPO_PUBLIC_HINTO_PUBLISHABLE_KEY`.

Replace older values of these names. Older setups named the publishable key `NEXT_PUBLIC_HINTO_INGEST_KEY` or `VITE_HINTO_INGEST_KEY`. Move the code that reads it to the new name, then delete the old variable and other Hinto variables from earlier setups, such as `NEXT_PUBLIC_HINTO_INGEST_URL`.

### 6. Set the deploy variables

If step 1 found a linked project and a signed-in CLI, set the same variables for production and preview yourself. Use the values from this pairing, including the API URL variable if you wrote one in step 5. For Vercel, run this once per variable and environment. It reads the value from `.env.local`, so the value never appears in your messages. Mark the signing secret as sensitive, and add `--force` to replace a value that already exists:

```bash theme={null}
grep '^HINTO_SIGNAL_SECRET=' .env.local | cut -d= -f2- | tr -d '\n' | vercel env add HINTO_SIGNAL_SECRET production --sensitive
```

Other CLIs work the same way, for example `netlify env:set` and `fly secrets set`. Do not link, create, or deploy a project. If there is no linked project or signed-in CLI, write the exact commands as one block for the person to paste, in the same form: each command reads its value from `.env.local`. Never put the signing secret in a message. On the sign-in provider path, set only the publishable key, and the API URL when you wrote one. The values take effect on the next deploy.

### 7. Install and wire the app

If the app already depends on `@gethinto/native-support-react` and has a token source (an endpoint or its sign-in provider) and a mounted conversation, do not install it again. Upgrade to the exact release, then check items 5 and 6 below. If the app has no journey, add it as item 5 describes. If it has a journey but no `<Support.HelpOffer>`, add the slot as item 6 describes. Section 10 of `node_modules/@gethinto/native-support-react/AGENT_INSTALL.md` lists what each upgrade changes. Then go to step 8.

Install the exact release with the project's package manager, then add the pieces the rest of this page describes. The package also ships a longer recipe with framework examples at `node_modules/@gethinto/native-support-react/AGENT_INSTALL.md`.

1. [Install the exact release](#install-the-exact-release), and import one of its two stylesheets once, as that section says.
2. Give the conversation a token. On the sign-in provider path, [use your sign-in provider's token](#use-your-sign-in-providers-token): copy that provider's `getToken` function next to the runtime, and add no server route. Otherwise, [issue a token on your authenticated server](#issue-a-token-on-your-authenticated-server) at `/api/support-token`. Include the signed-in user's `email`, `name`, and `company` when the app has them. Read them from the server-side session, never from the request.
3. [Create capture and conversation together](#create-capture-and-conversation-together), once per signed-in user. On the sign-in provider path, pass the provider's `getToken` and the provider's user ID as `subject`. The runtime sets the customer's identity in capture. Do not add `identify` calls with an email or another kind of identity.
4. [Place the conversation in your product](#place-the-conversation-in-your-product) at the entry point from step 2, with the uploads and status choices from step 2.
5. Instrument one journey, the critical journey from step 1, as [Journeys](/journeys) describes. Add calls only to the flow's existing handlers. Never add product behavior, server routes, or test hooks to make the flow fail. If the flow is a stub, or has no real point where it succeeds or fails, skip the journey and say so in the step 10 summary.
   * Call `capture.journey(key)` at the flow's entry point. It is idempotent, so a re-render is safe.
   * Call `attempt(action)` in the submit handler, before the request.
   * Call `fail({ action, category, feedback })` in the existing error branch. Map each error to one category: `client_validation`, `prerequisite`, `permission`, `third_party`, `network`, `server`, or `unknown`.
   * Call `succeed()` only after the server confirms, never on click.
   * When the submit control can be disabled, report `attempt` with `feedback` from a wrapper, because a disabled control emits no click.
   * Put only tokens in `metadata`, such as a plan or a provider. Never put user data there.
6. Place the help offer for that journey, unless the person said no in step 2, as [Offer help on the journey](/journeys#offer-help-on-the-journey) describes. The journey alone shows the customer nothing. A stuck customer sees an offer only where a slot is placed. Put `<Support.HelpOffer journey="..." topic="..." onRetry={...} />` in the component from step 1 that shows the failure, next to the control that fails, under the `Support.Provider` you mount in item 4. Use the same key as the journey, the step in the customer's words as `topic`, and the step's existing handler as `onRetry`. Keep it mounted, never inside a condition. It renders nothing until support has started for a signed-in customer. If you skipped the journey, skip the slot.

With an existing chat, unless the person chose a separate Support panel, follow [Use your own chat](/native-support-own-chat) in place of items 1, 3, and 4. Install only `@gethinto/browser-sdk` at the exact release on that page, get the token as item 2 describes, and connect with the bot's name. Add the handoff: a "Talk to a person" action, plus the `escalate_to_support` tool when the bot uses a language model. While a conversation is open, mirror every message the bot shows the customer and send the customer's messages to the team. Show the team's replies and the status inside the chat, and let the bot answer again when the conversation is resolved. Then instrument the journey as item 5 describes. For the help offer, draw it in your chat from `runtime.offers` instead of item 6, as [Offer help without the React kit](/journeys#offer-help-without-the-react-kit) describes.

Without React 19 and without an existing chat, install session capture only and identify each signed-in user with `{ kind: "external_id", value: user.id }`. Journeys work the same way, because capture starts identified. There is no help offer on this path, because there is no conversation to open. Say so in the step 10 summary.

### 8. Check your work

Run each check yourself. If one fails, fix the cause and run it again.

1. Run the project's typecheck and build.
2. Start the dev server in the background.
3. Request `/api/support-token` without a session. Expect `401`. On the sign-in provider path there is no endpoint: skip checks 3 and 4, and check 6 proves the token. For Supabase, request `https://<ref>.supabase.co/auth/v1/.well-known/jwks.json` instead and expect at least one key. No keys means the project still signs with its legacy JWT secret, which Hinto does not accept.
4. Sign in the way the app allows in development, such as a seeded or demo account, and request `/api/support-token` with that session. Expect `200` with `{ "token": ... }`. If signing in needs the person's own password, skip this check and say so.
5. Ask Hinto which app the publishable key belongs to, from each development origin. Set `HINTO_PUBLISHABLE_KEY` in your shell from the publishable key in `.env.local`. Expect `200` with your `applicationId`. The browser makes this same request when support starts.

```bash theme={null}
curl -sS "$HINTO_API_URL/v1/native-support/config" \
  -H "Authorization: Bearer $HINTO_PUBLISHABLE_KEY" \
  -H "Origin: http://localhost:3000"
```

| Status | `error` | What to do |
| - | - | - |
| 200 | | The key and the origin work. |
| 403 | `origin_not_allowed` | That origin is not on the websites list. Go back to step 3 and pair with every origin. A second pairing adds websites to the same app and keeps its keys. |
| 401 | `unauthorized` | The publishable key is wrong or revoked. Use `publishableKey` from the pairing. |
| 404 | `no_application` | The key belongs to no Signal app. Use `publishableKey` from the pairing. |
| 403 | `forbidden` | That is a secret key, not a publishable key. Use `publishableKey` from the pairing. |
| 403 | `application_disabled` | An admin paused Signal. Tell the person that an admin must choose **Resume Signal** in Settings > Hinto Signal. |

6. If you can drive a browser, open the app, sign in, and open support. Confirm the conversation loads and the console shows no Hinto errors. A conversation that asks the customer to sign in again means Hinto refused the token: see [Troubleshooting](/troubleshooting#support-asks-a-signed-in-customer-to-sign-in-again).
7. If you can drive a browser, run the journey once, the way a customer would, with `debug: true` passed to `initHinto`. Do not force a failure. The SDK writes its debug lines with `console.warn` and a `[Hinto]` prefix, so they are warnings, not errors. Each `ingest response: accepted N, duplicates N, rejected N` line covers one whole batch. In the Network tab, find the `/v1/ingest` request whose body contains the `journey` events. The check passes when the response line for that batch shows `rejected 0`. Remove `debug: true` afterwards. If you skipped the journey in step 7, skip this check.
8. If you can drive a browser, sign in, open the screen where the journey runs, and confirm it has an element with `data-hinto-part="help-offer"`. Signed out, the slot renders nothing. The element is empty until Hinto offers help, so do not expect a card. Do not force a failure to bring one. If you placed the offer in your own chat, skip this check.
9. Run the project's typecheck again.
10. Stop the dev server you started.

### 9. Stage the first ticket

Hinto investigates a ticket best when it has the customer's recorded visit. Before a customer writes in, stage one yourself: a real session in the running app, then one message. The ticket arrives in the Inbox with `origin = 'staged'`, which comes from the stage token, never from the message text. It raises no alert, has no composer, and its investigation is a free setup run that waits for the codebase like any ticket.

Run the stage command in the repository, from the directory that holds `.env.local` with `HINTO_STAGE_TOKEN` from step 5. It brings its own headless Chromium and opens the app (`http://localhost:3000` by default, then ports 5173 and 8080; pass `--url` for another). It runs your `--script` walk, if you pass one, then takes the newest session Signal reported. It writes in through the in-app conversation when the app exposes one at `window.hinto.conversation`, and otherwise posts the message to Hinto with the stage token. Both ways give the ticket the recorded visit, so you do not need to expose anything. It sends to the API URL from step 5, read from the shell's environment and then `.env.local`, or to `https://api.gethinto.com` when there is none. The value must be a full URL. Pass `--api` only to override it.

```bash theme={null}
npx @gethinto/signal@0.1.1 stage --reproduce 14 --message "Export fails after login: I click Export and nothing happens."
```

Pick the scenario in this order, and stop at the first that applies:

1. One of the imported tickets the prompt lists. Pass its number as `--reproduce`, do what the customer described, and write the same complaint in your own words.
2. A real defect you found while reading the code. Reproduce it the way a customer would.
3. A failure path under a blocked request: `--block "POST /api/export"` aborts that request in the browser, so the app's own error handling runs. Hinto records it as simulated and says so on the ticket.
4. A walk through the key flow from step 1 that ends with a question about it.

Never change code to cause a failure. Pass `--script walk.mjs`, a module exporting `async function run(page)`, when the flow needs clicks before the message; `page` is a Playwright page. When the app records only signed-in customers, as the setup on this page does, the walk must sign in first with a development account, such as a seeded or demo user. If signing in needs the person's own password, skip staging and say so in step 10. For a preview deploy that runs Signal, ask the person for a stage token from Hinto (Get started, ticket 4, "Issue a stage token") with the preview origin on the websites list, and pass `--url` and `--token`. The command refuses a production origin.

The stage token lasts 30 minutes from pairing. If it expired while you worked, the command says so. On the HTTPS path, pair again in step 3 for a new one. With the Hinto MCP server, call `create_stage_token`.

| Exit | Meaning |
| - | - |
| 0 | Staged. The command prints the ticket number. Tell the person: "Read the diff your agent made before you ship it." |
| 1 | A refusal Hinto or the command explained: no token, a spent or expired token, a production origin, no session within 30 seconds. Read the message and fix the cause. A spent token needs a new one from an admin. |
| 2 | Chromium could not start here. Tell the person: "Your agent could not open a browser here. Stage it from your app once Signal ships." |

### 10. Finish with a short summary

End with five short parts, in plain words. Write for the person, not for an engineer.

* **What I added:** one line naming the path you took (the Support panel, the existing chat, or session capture only), the files and the entry point, one line each, and one line naming the journey, where it reports, and where help is offered, for example "The Stripe connection journey reports from `ConnectStripe.tsx`, and a stuck customer is offered help next to the Connect button." If you skipped the journey, say why in one line, for example "I added no journey, because the Stripe connection page is a placeholder with no real success or failure."
  With the existing chat, the entry point is the file where the handoff lives.
* **What your customers will see:** one line per piece from [What Signal shows your customers](#what-signal-shows-your-customers) that you installed, saying where and when it appears. For example: "A **Support** item in the account menu opens a side panel. After a customer writes, they get an automatic reply and a status. When someone is stuck connecting Stripe, a help card appears next to the button." Always add: "Signal records what signed-in customers do in your app, never what they type. Check that your privacy policy covers it."
* **Decisions I made:** each **Decide** default from step 2 in one line, and each **Ask** answer you used a default for. Say where to change it, in plain words: in the code, or in Hinto under Settings > Auto-responses, or Settings > Hinto Signal for help offers. For how customers sign in, say which one you used, for example "Support trusts your Supabase sign-in, so I added no server code. An admin can change this under Sign-in provider in Settings > Hinto Signal."
* **What you need to do:** "Nothing." Or the one block of deploy commands from step 6. Or the one setting the sign-in provider needs, from [Use your sign-in provider's token](#use-your-sign-in-providers-token), such as moving a Supabase project off its legacy JWT secret or shortening an Auth0 token lifetime.
* **See it working:** for example, "Run `npm run dev`, sign in, and choose **Support** in the account menu. Send a message. It appears in your Hinto Inbox, and your reply there appears in the app."

Leave out origins and variable names unless the person must act on them. Name any old Hinto variables you deleted in one sentence.

### The Hinto MCP server is optional

You do not need the Hinto MCP server. If it is already connected and working, as an organization admin, you can call its `provision_hinto_signal` tool with the origins (0 to 10) instead of steps 3 and 4. On the sign-in provider path, also pass `identityProvider`, in the same shape as `identity_provider` in step 3. It returns the same fields as pairing, `publishableKey` and `identityProvider` included, except `stageToken`. A different provider than the one the app already trusts fails with `identity_provider_conflict`, as in step 4. Call `create_stage_token` for step 9 and write its token as `HINTO_STAGE_TOKEN`. If the server is missing or a call fails, pair over HTTPS. Never ask the person to connect it.

Use `reissueCredentials: true` only when the person says the credentials are lost. It issues a new publishable key and signing secret, revokes the old publishable key at once, and keeps the old signing secret working for 12 hours, so every website, mobile app, and backend must get the new values.

## Set up without an agent

1. In Hinto, open **Settings > Hinto Signal** as an organization admin.
2. If the organization has no Signal app yet, choose **Set up without a coding agent**. Hinto shows the publishable key and the backend values. The signing secret is shown once: copy the backend block now.
3. Put the publishable key in each website's and mobile app's env, as `NEXT_PUBLIC_HINTO_PUBLISHABLE_KEY`, `VITE_HINTO_PUBLISHABLE_KEY`, or `EXPO_PUBLIC_HINTO_PUBLISHABLE_KEY`. It is public and safe to ship.
4. Choose how the app proves who the customer is:
   * If customers sign in with Supabase, Firebase, Clerk, Auth0, or another OpenID provider, under **Sign-in provider** choose **Use a sign-in provider**, pick the provider, enter the values [Use your sign-in provider's token](#use-your-sign-in-providers-token) lists, and choose **Save**. The app then passes that provider's token, and needs no backend values.
   * Otherwise, put the backend block (`HINTO_SIGNAL_APP_ID`, `HINTO_SIGNAL_KID`, and `HINTO_SIGNAL_SECRET`) in your backend's env. Never send the secret to a browser or a phone.
5. Under **Products**, choose **Add website** for each website, with its exact origin, such as `https://app.example.com` and `http://localhost:3000`. A mobile app needs nothing here.

If the organization already has its Signal app, the page shows the same publishable key and the backend values without the secret. Use them for every new website or mobile app. Never create a second app. A new website may take up to one minute to pass the session capture cache.

Products lists each website and each mobile app with Live, Local, or Waiting. A mobile app appears after its first session.

**Roll key** replaces the publishable key. The old key keeps working for 7 days, so builds you already shipped keep working while you ship the new one. **Stop it now** ends it early. **Roll secret** replaces the signing secret and shows the new backend block once. The old secret keeps working for 12 hours, so update every backend that signs tokens within that time. Under **Sign-in provider**, **Remove** stops Hinto accepting that provider's tokens at once, and choosing another provider replaces it. Tokens your backend signs keep working either way.

## Install the exact release

```bash theme={null}
npm install @gethinto/browser-sdk@0.9.2 @gethinto/native-support-react@0.6.2
```

Import one stylesheet once, in the app's root: `@gethinto/native-support-react/styles.layered.css` when the app's CSS reset is layered or absent (Tailwind v4), or `@gethinto/native-support-react/styles.css` when it has an unlayered reset (Tailwind v3). The package supplies complete styled components. React 19 and a web browser are required. [Customization](/native-support-customization) maps your theme onto them.

## Use your sign-in provider's token

When customers sign in with one of these providers, Hinto accepts the provider's own token for the signed-in customer. The app needs no backend, no token route, and no backend values. Name the provider once: as `identity_provider` when you pair (step 3), as `identityProvider` with the MCP, or under **Sign-in provider** in Settings > Hinto Signal.

| Provider | What you send Hinto | `subject` | `getToken` returns |
| - | - | - | - |
| Supabase | `{"kind":"supabase","url":"https://<ref>.supabase.co"}` | `session.user.id` | The session's `access_token`. |
| Firebase | `{"kind":"firebase","projectId":"my-app-1234"}` | `user.uid` | `await user.getIdToken()`. |
| Clerk | `{"kind":"clerk","url":"https://example.clerk.accounts.dev"}` | `userId` | `await getToken()`, the session token. |
| Auth0 | `{"kind":"auth0","domain":"example.us.auth0.com","audience":"<API identifier>"}` | `user.sub` | An access token requested for that `audience`. |
| Another OpenID provider | `{"kind":"oidc","issuer":"https://...","audience":"<client ID or API audience>"}` | The token's `sub` | A signed JWT (an ID token or access token) with that `iss` and `aud`. |

* **Supabase:** `url` is the project URL the app's Supabase client uses. The project must sign with JWT signing keys, the default for new projects. A project that still uses the legacy JWT secret signs with HS256, which Hinto does not accept from a provider. Its owner migrates it in the Supabase dashboard under JWT Keys (**Migrate JWT secret**, then **Rotate keys**), or the app signs tokens on a backend instead.
* **Firebase:** `projectId` is `projectId` in the app's Firebase config.
* **Clerk:** `url` is the Frontend API URL from the Clerk dashboard, such as `https://clerk.example.com` in production or `https://example.clerk.accounts.dev` in development, with no path.
* **Auth0:** `domain` is the tenant domain, and `audience` is the identifier of the Auth0 API the app requests tokens for. A token requested without `audience` is not a JWT, and Hinto cannot read it. Auth0 issues API access tokens for 24 hours by default: set the API's maximum token lifetime to 43200 seconds (12 hours) or less.
* **Another OpenID provider:** `issuer` is exactly the `iss` value in the app's tokens. Hinto reads the keys from `<issuer>/.well-known/openid-configuration`, which must name the same issuer. `audience` is required.

Hinto checks each token the same way:

* It is signed with RS256, RS384, RS512, PS256, ES256, ES384, or EdDSA, by a key the provider publishes. Hinto never accepts HS256 from a provider.
* `iss` is the provider's issuer, and `aud` contains the audience (Supabase: `authenticated`; Firebase: the project ID; Auth0 and other providers: the `audience` you sent). Clerk tokens have no audience check.
* It has `sub`, `iat`, and `exp`, has not expired, and lives 12 hours or less.
* `sub` equals the `subject` the app passes to the SDK. Use the same ID for capture's `initialIdentity`.

Hinto takes the customer's `email` only when the provider marks it verified with a top-level `email_verified: true` claim, as Firebase ID tokens do. Supabase access tokens have no such claim, and their `user_metadata` is written by the signed-in user, so Hinto takes no email from a Supabase token. It takes the name from `name`, `user_metadata.full_name`, or `user_metadata.name`. It takes no company from a provider token. A ticket from a matching email arrives with that customer as the requester. Otherwise the name and email fill the ticket's **Keep** form.

A Signal app trusts one sign-in provider. Every website and mobile app of the organization shares it. Naming a different provider than the one already set fails with `identity_provider_conflict`, and an admin changes it in Settings > Hinto Signal. Tokens your backend signs keep working beside it.

The conversation calls `getToken` before each request, and with `true` after Hinto answers 401. Return the provider's current token, and a fresh one when the argument is `true`. Throw when nobody is signed in: the conversation then shows its sign-in state. Copy the function for your provider:

```js theme={null}
// Pass one of these as `getToken`. Hinto calls it with `true` after a 401: return a fresh token then.
// Each throws when nobody is signed in, which shows the conversation's sign-in state.

// Supabase (@supabase/supabase-js). subject: session.user.id
export const supabaseToken = (supabase) => async (forceRefresh) => {
  const { data } = forceRefresh
    ? await supabase.auth.refreshSession()
    : await supabase.auth.getSession();
  if (!data.session) throw new Error("Support authentication required");
  return data.session.access_token;
};

// Firebase (firebase/auth). subject: auth.currentUser.uid
export const firebaseToken = (auth) => async (forceRefresh) => {
  if (!auth.currentUser) throw new Error("Support authentication required");
  return auth.currentUser.getIdToken(forceRefresh);
};

// Clerk: `getToken` from useAuth() or Clerk.session. subject: userId
export const clerkToken = (getToken) => async (forceRefresh) => {
  const token = await getToken({ skipCache: forceRefresh });
  if (!token) throw new Error("Support authentication required");
  return token;
};

// Auth0 (@auth0/auth0-react or @auth0/auth0-spa-js). `audience` is the API identifier you gave
// Hinto. subject: user.sub
export const auth0Token = (getAccessTokenSilently, audience) => (forceRefresh) =>
  getAccessTokenSilently({
    authorizationParams: { audience },
    cacheMode: forceRefresh ? "off" : "on",
  });
```

For example, with Supabase: `auth: { subject: session.user.id, getToken: supabaseToken(supabase) }`.

## Issue a token on your authenticated server

When the app signs its own tokens, or uses a sign-in provider Hinto does not accept, use your existing server session to obtain a stable customer ID. Sign an HS256 JWT with the backend values from Settings > Hinto Signal: `HINTO_SIGNAL_KID` goes in the header as `kid`, and `HINTO_SIGNAL_SECRET` signs it. The token uses that customer ID for `sub`, the Signal app ID from `HINTO_SIGNAL_APP_ID` for `aud`, and an expiration no later than 12 hours. The helper below uses one hour. Return `{ token }` with `Cache-Control: no-store` from an authenticated `/api/support-token` route. Return 401 when signed out. The browser must never receive the signing secret.

Also put the signed-in user's `email`, `name`, and `company` in the token when your app knows them. Read them from the same server session or user record that gives you `sub`. Never read them from the request body, the query string, or anything else the browser sends. Each claim is optional. Pass `null` or leave it out when the value is unknown.

With these claims, a ticket from a user whose email matches an existing customer in your organization arrives with that customer as the requester. For anyone else, Hinto fills the name, email, and company into the ticket's **Keep** form. Hinto never creates a customer from the claims alone. After an operator confirms with **Keep**, later tickets from that user arrive known. A token with only `sub` still works, but every ticket from it arrives as an unknown contact.

```js theme={null}
import { createHmac } from "node:crypto";

// `profile` comes from your server session or user record, never from the request.
export function signSupportToken(subject, appId, kid, secret, profile = {}) {
  if (!subject || !appId || !kid || !secret)
    throw new Error("Missing authenticated subject or signer configuration");
  const { email = null, name = null, company = null } = profile;
  const now = Math.floor(Date.now() / 1000);
  const encode = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
  const claims = { sub: subject, email, name, company, aud: appId, iat: now, exp: now + 3600 };
  const input = `${encode({ alg: "HS256", typ: "JWT", kid })}.${encode(claims)}`;
  return `${input}.${createHmac("sha256", secret).update(input).digest("base64url")}`;
}
```

## Create capture and conversation together

Create one runtime per signed-in customer after the browser mounts. The browser needs only the publishable key: pass it as `apiKey` to `initHinto` and as `ingestKey` to `connectConversation`. `connectConversation` asks Hinto which organization and Signal app that key belongs to, and Hinto answers only on a website from the list in Settings > Hinto Signal. Pass the same stable customer ID to capture identity and token authentication. The browser does not pass the user's name or email. Those travel in the token. The example uses `/api/support-token`; adapt that path to your backend. On the sign-in provider path, replace the example's `getToken` with your provider's function from [Use your sign-in provider's token](#use-your-sign-in-providers-token), and pass the provider's user ID as `customerId`. `startSupport` rejects with a `ConversationConnectError` whose `code` names the problem, such as `origin_not_allowed`. Call `switchCustomer` before showing another signed-in account, and `destroy` on unmount. Keep `capture` for [journeys](/journeys).

The runtime owns the customer's identity in capture: `switchCustomer` moves capture to the new customer, and `switchCustomer(null)` at sign-out ends the session. Do not call `identify` with an email or any other kind of identity once support runs. It replaces the identity the conversation checks, and every send then fails. Pass `appVersion`, such as your release commit, and `environment` to `initHinto` when the app knows them, so investigations can tell which release a customer used. [Install the browser SDK](/browser-sdk#configuration) lists every capture option.

`startSupport` can reject with a `ConversationConnectError`. Its `code` is one of these:

| `code` | Meaning |
| - | - |
| `unauthorized` | The key is wrong or revoked, or it is a secret key. Use the publishable key. |
| `no_application` | The publishable key belongs to no Signal app. |
| `origin_not_allowed` | The page's origin is not on the websites list. Pair again with it (step 3), or an admin adds it in Settings > Hinto Signal. |
| `application_disabled` | An admin paused Signal in Settings > Hinto Signal. |
| `unavailable` | Hinto did not answer (offline, a rate limit, or a server error), or refused for a reason this SDK version does not know. Try later, then update the SDK. |
| `invalid_response` | The answer was not one the SDK can read, often from a proxy between the app and Hinto. Check the request in the network tab. |

```ts theme={null}
import { initHinto } from "@gethinto/browser-sdk";
import { connectConversation } from "@gethinto/browser-sdk/conversation";
import { createSupportUI } from "@gethinto/native-support-react";

export async function startSupport(options: {
  customerId: string;
  publishableKey: string;
  apiUrl?: string;
  tokenUrl?: string;
}) {
  const getToken = async () => {
    const response = await fetch(options.tokenUrl ?? "/api/support-token", {
      credentials: "same-origin",
      cache: "no-store",
    });
    if (!response.ok) throw new Error("Support authentication required");
    const data = (await response.json()) as { token: string };
    return data.token;
  };
  const capture = initHinto({
    apiKey: options.publishableKey,
    apiUrl: options.apiUrl,
    initialIdentity: { kind: "external_id", value: options.customerId },
  });
  const client = await connectConversation({
    capture,
    ingestKey: options.publishableKey,
    apiUrl: options.apiUrl,
    auth: {
      subject: options.customerId,
      getToken,
    },
  }).catch((error: unknown) => {
    capture.shutdown();
    throw error;
  });
  const ui = createSupportUI(client);
  return {
    capture,
    ui,
    switchCustomer(subject: string | null) {
      client.updateAuth(
        subject
          ? {
              subject,
              getToken,
            }
          : null,
      );
    },
    destroy() {
      ui.destroy();
      client.destroy();
      capture.shutdown();
    },
  };
}
```

## Place the conversation in your product

Render `Support.Provider` with the controller and current subject, high enough in the tree to cover the entry point and every help offer slot. An entry button calls `useSupportEntry().open(opener)`; that action alone does not create a ticket. It can show `useSupportEntry().unreadCount`, the team's replies the customer has not seen. Place this conversation inside your page or existing Sheet, and render it while `useSupportEntry().isOpen` is true. Give `onReauthenticate` an action that opens your sign-in flow.

When support lives on its own page instead of a Sheet, navigate there when `isOpen` turns true, and call `close()` when the customer leaves it. Contact support on a help offer opens support the same way as the entry button.

Inside a Sheet or Dialog, the conversation's header replaces the Sheet's header. Pass the Sheet's title component as `titleRender`, for example `titleRender={<SheetTitle />}`, and its close button as `headerActions`. Hinto renders its title inside that component, so the dialog has one title for screen readers. Turn off the Sheet's own close button, for example with `showCloseButton={false}` on shadcn's `SheetContent`. The package's `AGENT_INSTALL.md` has the full Sheet example.

```tsx theme={null}
import { Support } from "@gethinto/native-support-react";

// The default composition: messages with the status row, and a composer with uploads.
export function ProductConversation() {
  return <Support.Conversation className="customer-support" />;
}
```

`Support.Conversation` without children renders the default composition: the messages, with the status row last, and a composer with uploads. To leave uploads out, compose it yourself with `Support.Messages` and a `Support.Composer` that holds `Support.ComposerInput` and `Support.Send` but no `Support.Attach`. To hide the status row, pass `status={null}` to `Support.Messages`.

A deliberate Send creates or appends to a conversation. Inbox human replies become visible only after delivery succeeds. Enter sends; Shift+Enter adds a line; touch-only keyboards use the Send button. Send empties the composer at once, and the message shows as sending in the transcript while the customer types the next one. Retry retains the same pending command, so a lost response does not create a duplicate. After resolution, Start new creates a distinct thread. Unread is last-known for this tab, not a cross-device read receipt.

## Verify the integration

This is the person's check. A coding agent's staged ticket (step 9 of the runbook above) is not it: the person's own first message is.

Sign in to your product as a customer, send a message, reply from the Hinto Inbox, and confirm the reply arrives in your product. If you instrumented a journey, fail it once before you send. The ticket shows the journey above the session evidence.

To see the help offer, sign in as a different customer and fail the journey twice in a row on its screen. When Hinto Signal judges the customer stuck, a help card appears next to the step within a few seconds. Choose Contact support and confirm one ticket arrives with the journey attached. Hinto waits between offers for the same customer, so use a new customer for each try.

See [security and identity](/native-support-security), [customization](/native-support-customization), and [troubleshooting](/troubleshooting). The first release does not include a floating launcher, AI customer responder, or Vue or Svelte adapter. For an iOS or Android app, see [Install the React Native SDK](/react-native). Browser and physical mobile support claims follow recorded release checks, not desktop emulation.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.