Skip to main content
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, 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. 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.
  • Your backend. It signs a token with the backend values. See 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 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.

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.
  • 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 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 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.
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, 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.
A 201 response has these fields: 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 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:
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. 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. 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:
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, 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: copy that provider’s getToken function next to the runtime, and add no server route. Otherwise, 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, 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 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 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 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 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 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.
  1. 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.
  2. 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.
  3. 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.
  4. Run the project’s typecheck again.
  5. 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.
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.

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 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, 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 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

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 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.
  • 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:
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.

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, 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. 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 lists every capture option. startSupport can reject with a ConversationConnectError. Its code is one of these:

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.
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, customization, and 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. Browser and physical mobile support claims follow recorded release checks, not desktop emulation.