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

FlowWithout localhost modeWith localhost mode
App handshakeHint's server POSTs the signed payload to your handshake_urlYour browser POSTs the same signed payload to your localhost_handshake_url
App surface iframeHint frames the surface's source_urlHint frames the surface's localhost_source_url
Activation redirect / connectThe practice is sent to the backend's redirect_urlThe practice is sent to the backend's localhost_redirect_url
WebhooksDelivered server-to-server to your webhook endpointAlso replayed from your browser to the backend's localhost_webhook_url
API versionRequests resolve to your pinned versionRequests 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://. The localhost_* fields require the http scheme, so https://localhost:3000 is rejected while http://localhost:3000 is 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 portalAPI fieldLives on
Sandbox Handshake URLlocalhost_handshake_urlthe product's app
Sandbox Source URL (one per surface)localhost_source_urleach app surface
Sandbox Redirect URL / Sandbox Connect URLlocalhost_redirect_urlthe backend
Sandbox Webhook URLlocalhost_webhook_urlthe backend
Localhost API versionlocalhost_pinned_versionthe 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.md

Set 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:

  1. 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.
  2. The X-Hint-Signature header is computed the same way, so verify it exactly as described in Webhooks Security.
  3. You return { "session_key": "..." }, and Hint appends it to your localhost_source_url when 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_redirect opens localhost_redirect_url + authorization_code in a new window.
  • automatic_headless has your browser POST to localhost_redirect_url + authorization_code.
  • manual backends 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 POST with Content-Type: application/json; charset=utf-8 and an X-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 tab

Replay 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 Origin or Referer host is localhost, 127.0.0.1, 0.0.0.0, or any *.localhost host. If your client sends neither header, send Hint-Localhost-Mode: true instead.
  • Matching requests resolve to the localhost pin; everything else resolves to your normal pinned version.
  • The resolved version comes back on the Hint-Version response 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 APIs

If you need to test from a real phone, or your app uses APIs that require a secure context (camera, microphone, geolocation), http://localhost will not work. Point your real handshake_url and source_url at an https tunnel instead and leave localhost mode off.

Troubleshooting

SymptomLikely causeFix
No Localhost pill in the navThe session is not a sandbox session affiliated with a partner, or the partner has nothing routable configuredSign 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 rejectedYou 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 errorLocal server down, or CORS not configuredStart the server and allow the origin named in the error, including X-Hint-Signature
localhost URLs not configuredOnly one of the surface's two URLs is setSet both Sandbox Handshake URL and Sandbox Source URL
"No Sandbox Redirect URL is set for this app" on activationA connect-URL backend has no localhost_redirect_urlSet the Sandbox Redirect URL, or turn localhost mode off and retry
No webhooks arriving locallyNo Sandbox Webhook URL, no Hint tab open, or the event predates enablingSet the URL, keep a Hint tab open, and trigger a fresh event
Webhook signature does not verifyVerifying against the wrong backend's signing keyConfirm the backend's webhook signing key; the replay signature uses it and matches production
Wrong response shape locallyThe localhost version pin is set, or is not being detectedCheck the Hint-Version response header and send Hint-Localhost-Mode: true if your client sends no Origin or Referer

Related


Did this page help you?