App Best Practices
Map to clinical interaction endpoints
Use clinical interactions to let users jump between Hint and your embedded experience.
Create a clinical interaction
Reference Create Partner Interaction. Interactions provide a durable pointer for the user to reopen your iframe later.
const createInteraction = async patientId => {
const response = await fetch(
`https://api.hint.com/v1/provider/patients/${patientId}/interactions`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'partner',
status: 'draft'
})
}
);
return response.json();
};Attribute the interaction to one of your products
If you list more than one product on the marketplace, tell Hint which one the interaction belongs to by
sending partner_product_id. Hint uses it to reopen the right app when a user returns to the note later.
body: JSON.stringify({
type: 'partner',
status: 'draft',
partner_product_id: 'ppro-...'
})Hint resolves the product in this order:
partner_product_id, when you send one.- The product whose surface the call came from, when your app made it from inside an embedded surface.
- The practice's only installed product of yours, when it has exactly one.
If a practice has installed several of your products and neither of the first two applies, the create fails
with 400 and a message beginning Multiple products are installed for this practice. Send
partner_product_id on every create if your backend knows which product it is acting for - it is accepted at
single-product practices too, so there is no need to make it conditional. Hint validates it against that
practice's installed products, so naming one the practice has not installed also returns 400.
Update a clinical interaction
Use Update Partner Interaction to update the interaction state (e.g., signed vs. draft) when your workflow completes.
const updateInteraction = async (patientId, interactionId, updates) => {
const response = await fetch(
`https://api.hint.com/v1/provider/patients/${patientId}/interactions/${interactionId}`,
{
method: 'PATCH',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
}
);
return response.json();
};Integrate with the Hint UI
Use the Hint Marketplace JS SDK to read the current patient and user, and to close the iframe when your workflow ends. The SDK also exposes deep-link helpers (queryParams, fragment) so you can drive consistent navigation between Hint users.
The patient is fixed for the lifetime of a surface. Read HintSDK.currentPatient inside your HintSDK.init(callback) callback and treat it as constant - if the user switches patients on a surface that allows it, Hint remounts your embed with a fresh init() rather than notifying the running page. Keep any state that must survive that reload on your own backend, keyed by practice and patient ID.
Report your height on clinical surfaces
Clinical surfaces (clinical_interaction, clinical_chart) size the embed iframe only from your app's height reports. Include the Hint JS SDK on every embedded page — it reports height automatically via a ResizeObserver. Without it, your surface renders clipped to a ~150px strip inside the note window. This also applies to core_page surfaces with auto-adjust height disabled.
Using device capabilities (camera, microphone, geolocation)
Embedded surfaces run in a cross-origin iframe, so browsers block capabilities like navigator.mediaDevices.getUserMedia unless Hint delegates them to your app's origin. Delegation is opt-in per app: set browser_allow_list when updating your app (Update App) — for example {"app": {"browser_allow_list": ["camera", "microphone"]}}. Supported values are camera, microphone, and geolocation. Hint then renders your embed iframes with a matching Permissions Policy allow attribute.
You can also set this without calling the API: open your product's App Settings in Hint and use the Browser Capabilities toggles. The toggles and browser_allow_list write the same field, so use whichever path fits your workflow.
- The end user still sees the browser's standard permission prompt — the list delegates the capability, it does not grant permission.
- The
allowattribute is rendered when a surface is embedded, so changes tobrowser_allow_listonly apply to newly opened surfaces — reload any already-open surface. - Without it,
getUserMediarejects immediately withNotAllowedErrorand no prompt is shown. - The handshake payload includes the current
browser_allow_list, so your app can preflight before callinggetUserMedia. On aNotAllowedError, check it first: if the capability is missing from the list, Hint hasn't delegated it (fix: enable it under App Settings > Browser Capabilities); if it's present, the user denied the browser prompt (fix: the browser's site permissions). Both cases throw the same error, so this field is the only way to show the right message. - Current iOS honors the delegation (camera, microphone, and geolocation verified in iOS Safari, including switching cameras with
facingModeand zooming withapplyConstraints). Older iOS versions may still blockgetUserMediain cross-origin iframes, so a fallback such as<input type="file" accept="image/*" capture="environment">is still worth providing.
Preflighting against the handshake value lets you tell the two failure modes apart and show the user the fix that actually applies:
const allowed = handshake.browser_allow_list ?? [];
const startCamera = async () => {
if (!allowed.includes("camera")) {
throw new Error(
"Camera access is not enabled for this app in Hint. Enable it under App Settings > Browser Capabilities, then reopen this surface."
);
}
try {
return await navigator.mediaDevices.getUserMedia({
video: { facingMode: "environment" },
});
} catch (error) {
if (error.name === "NotAllowedError") {
throw new Error(
"Your browser blocked camera access for this site. Allow it in your browser's site permissions and try again."
);
}
throw error;
}
};Security and PHI handling
- Treat the handshake
access_tokenas a secret and scope it to the session (do not log it). - Verify
X-Hint-Signaturefor the handshake payload using Webhooks Security. - Avoid storing or logging PHI unless you need it for the workflow. Prefer using IDs and fetching details on demand.
Updated 20 days ago

