Localhost Mode
Route your product's handshake, app surfaces, activation redirect, and webhooks to a server on your own machine. No tunnel, no hosted dev server.
Building a Marketplace product normally means Hint has to reach your code over the public internet. Hint POSTs the handshake to your server, frames your surface from an https:// URL, sends practices to your activation redirect, and delivers webhooks to your endpoint. During development none of that exists yet, so teams reach for a tunnel (ngrok, Cloudflare Tunnel) or keep redeploying to a staging server just to see a change.
Localhost mode removes that step. Turn it on and Hint routes your product's outbound traffic to URLs on your machine instead, so you develop against the code running in your editor.
Two properties make it safe to use on a shared sandbox:
- It is per browser session. Turning it on affects only your signed-in session. Teammates using the same sandbox partner, and any practice users testing your app, keep hitting your deployed URLs.
- It is sandbox only. Live partners cannot configure or enable it. Your production configuration is never touched, and the sandbox URLs live alongside your existing URLs rather than replacing them.
This applies whether you are a Marketplace partner building an integration or an app, or a practice developer team with Practice API Access - that also comes with a partner account and sandbox practices. Practice teams most often use it for webhook testing; the app and activation routing applies only if you publish a product with an app or use a connect-URL backend.
What gets routed
| Flow | Without localhost mode | With localhost mode |
|---|---|---|
| App handshake | Hint's server POSTs the signed payload to your handshake_url | Your browser POSTs the same signed payload to your localhost_handshake_url |
| App surface iframe | Hint frames the surface's source_url | Hint frames the surface's localhost_source_url |
| Activation redirect / connect | The practice is sent to the backend's redirect_url | The practice is sent to the backend's localhost_redirect_url |
| Webhooks | Delivered server-to-server to your webhook endpoint | Also replayed from your browser to the backend's localhost_webhook_url |
| API version | Requests resolve to your pinned version | Requests from localhost resolve to a separate localhost pin |
What it does not change
- It is not a proxy for calls you make to Hint. Your requests to
/api/partner/*and/api/provider/*are unaffected, apart from the optional localhost API version pin. - It does not stop normal webhook delivery. Deliveries still go to your configured webhook endpoint, and the replay is an extra copy sent to your machine. Treat webhooks as at-least-once, exactly as you would in production.
- It does not change signatures or payload contracts. The handshake and webhook bodies your local server receives are signed the same way and carry the same shape as production, so the verification code you ship is the code you test.
Before you start
- A sandbox partner. The
localhost_*fields reject any attempt to set them on a live partner. - A sandbox session affiliated with that partner: signed into your sandbox partner organization, or into a sandbox practice that belongs to it.
- A local server reachable over
http://. Thelocalhost_*fields require thehttpscheme, sohttps://localhost:3000is rejected whilehttp://localhost:3000is accepted. - CORS on your local server. Several of these flows are driven from your browser rather than from Hint's servers, so your local endpoints must accept cross-origin requests from the Hint portal origin. See CORS requirements.
The toggle becomes available once your partner has something to route: a manual backend, or a Sandbox Redirect URL, Sandbox Handshake URL, or Sandbox Source URL set. A Sandbox Webhook URL on its own does not enable the toggle, so if webhooks are all you are testing and your backend uses automatic_redirect or automatic_headless, set a Sandbox Redirect URL too.
Configure your sandbox URLs
In the Partner Portal, open your product and go to the Localhost Mode tab. Every field is optional; configure only the flows you are working on.
| Field in the portal | API field | Lives on |
|---|---|---|
| Sandbox Handshake URL | localhost_handshake_url | the product's app |
| Sandbox Source URL (one per surface) | localhost_source_url | each app surface |
| Sandbox Redirect URL / Sandbox Connect URL | localhost_redirect_url | the backend |
| Sandbox Webhook URL | localhost_webhook_url | the backend |
| Localhost API version | localhost_pinned_version | the partner |
You can also set the URL fields through the public API. Each field sits next to its production counterpart on the same endpoint.
Reference: Update Partner Backend, Update Product App, Update Product App Surface.
If you work with an AI coding agent, the Hint Marketplace agent skill can set these fields for you as part of building or retrofitting your app. Install the router skill and it will route to the right sub-skill:
https://raw.githubusercontent.com/hinthealth/marketplace-skill/main/SKILL.mdSet the Localhost API version on the Localhost Mode tab in the portal.
Turn it on
Once at least one sandbox URL is configured, a Localhost pill appears in the top navigation of your sandbox session. Flip the toggle and the pill turns green.
The pill is the whole control surface:
- Green means your session is routing that partner's apps to localhost.
- A replay counter appears next to it while a Sandbox Webhook URL is configured, counting the deliveries forwarded to your machine.
- The tooltip names the partner being routed.
Turning it off restores production routing immediately, and the webhook replay counter resets. Nothing about the toggle is visible to, or inherited by, any other session.
App surfaces
Production embedding is a server-to-server handshake: Hint POSTs the signed payload to your handshake URL, reads back a session_key, and frames your source_url with that key appended. Hint's servers cannot reach http://localhost, so localhost mode moves the handshake into your browser, which can.
sequenceDiagram
participant B as Your browser
participant H as Hint API
participant L as Your local server
B->>H: POST localhost_embed
H-->>B: signed handshake body and signature
B->>L: POST localhost_handshake_url
L-->>B: session_key
B->>H: POST complete_localhost_embed
H-->>B: iframe for localhost_source_url with session_key
Everything your handshake endpoint sees is unchanged from production:
- The body is the same signed handshake payload, carrying the user, practice, integration, installation, product,
partner_roles,access_context, and a short-lived access token. - The
X-Hint-Signatureheader is computed the same way, so verify it exactly as described in Webhooks Security. - You return
{ "session_key": "..." }, and Hint appends it to yourlocalhost_source_urlwhen framing the iframe.
A surface needs both localhost_handshake_url and localhost_source_url set. With only one of them, the embed fails with localhost URLs not configured rather than silently falling back to production.
Activation
When a practice activates your product while your session is in localhost mode, the activation step targets localhost_redirect_url instead of redirect_url, with the authorization_code appended exactly as in production:
automatic_redirectopenslocalhost_redirect_url+authorization_codein a new window.automatic_headlesshas your browser POST tolocalhost_redirect_url+authorization_code.manualbackends have no connect URL, so there is nothing to route.
There is no fallback here, by design. If a connect-URL backend has no Sandbox Redirect URL set, Hint blocks the activation and tells you to set one instead of quietly sending the practice to your production callback and creating a real connection. The install action reports itself as disabled with the same reason.
See Integration Activation for the underlying flow.
Webhooks
Webhook delivery stays server-to-server; your endpoint keeps receiving events as usual. On top of that, while localhost mode is on and a Sandbox Webhook URL is configured, the Hint tab you have open polls for recent deliveries and replays each one to your machine.
flowchart LR
E[Event in Hint] --> D[Normal delivery to your webhook endpoint]
E --> R[Delivery recorded]
R --> P[Hint tab polls every 5s]
P --> L[Browser POSTs to localhost_webhook_url]
What to expect:
- Polling runs every 5 seconds, only while localhost mode is on and a Sandbox Webhook URL is set.
- Only deliveries created after you flip the toggle replay. Enabling primes the cursor to the newest delivery, so you never get flooded with a backlog.
- Up to 50 deliveries per poll, replayed oldest-first.
- The request is
POSTwithContent-Type: application/json; charset=utf-8and anX-Hint-Signature: sha256=<hmac>header signed with that backend's webhook signing key. Verify it exactly as you verify production webhooks. - Failures are ignored. Each replay times out after 10 seconds and the result is discarded, so a local server that is down or throwing never stalls polling. Restart it and the next event replays.
- Only sandbox deliveries for your own partner group are replayed, and only for backends that have both a Sandbox Webhook URL and a webhook signing key configured.
The replay needs an open tabReplay is driven by the Hint tab in your browser. Close it, sign out, or toggle localhost mode off, and replaying stops. Events that occur while it is stopped are still delivered normally to your webhook endpoint, but they are not queued up for your machine.
Pin a different API version for localhost
This is the one part of localhost mode that applies to requests you make to Hint rather than to Hint's traffic to you. Your /api/partner/* calls can resolve to a different public API version when they come from localhost, so you can develop against a newer version locally while everything you have deployed keeps running on the version it was written for.
Set Localhost API version on the same Localhost Mode tab. Then:
- It applies to your
/api/partner/*requests. - Hint treats a request as coming from localhost when the
OriginorRefererhost islocalhost,127.0.0.1,0.0.0.0, or any*.localhosthost. If your client sends neither header, sendHint-Localhost-Mode: trueinstead. - Matching requests resolve to the localhost pin; everything else resolves to your normal pinned version.
- The resolved version comes back on the
Hint-Versionresponse header, so you can confirm which one you got. - Webhook replays are re-rendered at the localhost pin, so the payload your local code receives is shaped for the version it targets, not the version the stored delivery was rendered at.
Leave the field on Use pinned version (default) to keep localhost requests on your normal version.
Unlike the routing above, this does not depend on the Localhost pill being toggled on. It is resolved per request, from the request's own origin or header, so it works whether or not any browser session is currently routing to localhost.
CORS requirements
Because the handshake, headless connect, and webhook replay are all sent from your browser, your local endpoints must allow cross-origin requests from the Hint portal making the call.
There is more than one such origin. Which portal calls your local server depends on the surface type: the provider portal for core_page and settings, the clinical portal for clinical_interaction and clinical_chart. Do not hardcode a single origin.
Instead, keep an allowlist containing both portal origins for your environment, echo the request's Origin header back when it matches, and vary on it:
Access-Control-Allow-Origin: <the matching Origin from the request>
Vary: Origin
Access-Control-Allow-Headers: Content-Type, X-Hint-Signature
Your endpoints must also answer the OPTIONS preflight. If the handshake fails on CORS, the app frame prints an error naming the exact origin to allow, so you can copy it from there.
Localhost mode cannot help with secure-context APIsIf you need to test from a real phone, or your app uses APIs that require a secure context (camera, microphone, geolocation),
http://localhostwill not work. Point your realhandshake_urlandsource_urlat anhttpstunnel instead and leave localhost mode off.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No Localhost pill in the nav | The session is not a sandbox session affiliated with a partner, or the partner has nothing routable configured | Sign into your sandbox partner or a sandbox practice belonging to it. If your only sandbox URL is the webhook one, add a Sandbox Redirect URL as well |
Saving a localhost_* URL is rejected | You are on a live partner, or the URL uses https:// | These fields are sandbox only and must use http:// |
| App frame shows a localhost handshake error | Local server down, or CORS not configured | Start the server and allow the origin named in the error, including X-Hint-Signature |
localhost URLs not configured | Only one of the surface's two URLs is set | Set both Sandbox Handshake URL and Sandbox Source URL |
| "No Sandbox Redirect URL is set for this app" on activation | A connect-URL backend has no localhost_redirect_url | Set the Sandbox Redirect URL, or turn localhost mode off and retry |
| No webhooks arriving locally | No Sandbox Webhook URL, no Hint tab open, or the event predates enabling | Set the URL, keep a Hint tab open, and trigger a fresh event |
| Webhook signature does not verify | Verifying against the wrong backend's signing key | Confirm the backend's webhook signing key; the replay signature uses it and matches production |
| Wrong response shape locally | The localhost version pin is set, or is not being detected | Check the Hint-Version response header and send Hint-Localhost-Mode: true if your client sends no Origin or Referer |
Related
Updated about 22 hours ago

