Launch context
Implement the callback ChartHero calls after decrypting ctx.
Launch
Create encrypted launch URLs from an external system into ChartHero.
v1.0This page is the field-level reference for the encrypted launch link — the JWE header, the payload fields, and the generation checklist. For how the link fits the end-to-end launch, start with Launch Flow.
Your system creates launch links in its UI. Use the encrypted launch alias saved on the ChartHero adapter installation.
https://app.charthero.ai/launch/{launch_alias}?ctx=<compact-jwe>
{launch_alias} is the same value as the launch_profiles.encrypted.route_alias
field in your installation config — "launch alias" and route_alias refer to the
same thing.
Production launch URLs must contain exactly one ctx query parameter. Do not send raw patient, encounter, provider, demographic, or clinical values as URL query parameters.
Minimal URL shape:
https://app.charthero.ai/launch/acme-care-prod?ctx=eyJhbGciOiJkaXIi...
The ctx value is a compact JWE encrypted for ChartHero.
{
"alg": "dir",
"enc": "A256GCM",
"kid": "launch-key-2026-06",
"typ": "charthero-org-integration-launch+jwe"
}
Only alg, enc, kid, and typ are allowed in the protected header, and all four fields are required. alg must be dir, enc must be A256GCM, and typ must be charthero-org-integration-launch+jwe.
The decrypted payload includes issuer, audience, time bounds, replay protection, top-level actor evidence, and top-level adapter-owned launch_state. Compute iat and exp when the launch link is created; exp must be after iat and no more than five minutes later.
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: 'acme-care-platform',
aud: 'charthero',
iat: now,
exp: now + 300,
jti: '01JZ123456789ABCDEFGHJKLM',
actor: {
actor_id: '11111111-1111-4111-8111-111111111111',
email: 'clinician@example.com',
external_ids: [
{
source: 'provider_acme_care_prod',
type: 'provider_id',
value: 'prov-456',
},
],
},
launch_state: {
appointment_id: 'apt-789',
correlation_id: 'partner-click-123',
},
};
The launch_state keys here are illustrative. The object is yours — use whatever
handles your adapter needs to rebuild context; ChartHero never interprets them.
Rules:
iss, aud, iat, exp, jti, and launch_state are required.launch_state must be a JSON object. It may be {} when your adapter can resolve the launch from other evidence.actor is launch-access evidence for the ChartHero user expected to open the link. When provider linking is required, include at least one of actor.actor_id, actor.email, or actor.external_ids[]. When provider linking is disabled, actor.external_ids[] is not needed; ChartHero accepts schema-valid values but ignores them for provider linkage.exp - iat must be no more than five minutes.jti must be unique per launch — ChartHero performs the replay check and rejects any reused jti. Because the jti is surfaced back to your callback as launch_id, it must be 16 to 128 characters using only letters, numbers, ., _, :, or - (a UUID or ULID works well).launch_state is opaque and owned by your adapter. It travels encrypted inside the launch link and ChartHero never interprets it, so it may carry the real values your adapter needs to rebuild context. Keep it within the payload size cap and avoid long-lived secrets, credentials, and large records. ChartHero does not act on launch_state, so put anything ChartHero must open in the launch-context response, not here.context, patient, or encounter in encrypted launch payloads. Patient and encounter identifiers belong in the verified launch-context response.{source,type,value} with the enabled provider identifier source. The currently published provider helper contract uses type: "provider_id".external_ids[] list may include at most 16 identifiers.Actor validation is strict. If you send more than one actor criterion while provider linking is optional or required, every provided criterion must match the authenticated ChartHero user. actor_id must match the ChartHero public actor id, email is normalized and compared case-insensitively against a verified ChartHero identity, and every external_ids[] item must match the user's provider linkage when provider linking is enabled. Direct actor fields can help ChartHero validate or repair missing provider links when verified-email auto-linking is approved, but they do not override conflicting external IDs when provider linking is enabled. If provider linking is disabled, ChartHero ignores actor external IDs for provider linkage after schema validation; direct actor fields such as actor_id and email still must match when present.
When creating a launch URL:
iat to the current Unix timestamp in seconds.exp no more than five minutes after iat.jti (ChartHero performs the replay check).kid in the protected header.ctx query parameter.Generate or import the launch keyset from the saved adapter detail page before testing. Do not use the example key or example identifiers in production.
This end-to-end snippet reads the active key from your launch keyset, mints an
encrypted ctx, and builds the launch URL. It uses
jose (npm i jose); any compliant JWE library
works.
import { CompactEncrypt, base64url } from 'jose';
import { randomUUID } from 'node:crypto';
// 1. Your launch keyset, generated in ChartHero or imported during setup.
// Select the active key.
const keyset = {
version: 1,
keys: [
{
kid: 'launch-key-2026-06',
kty: 'oct',
alg: 'dir',
enc: 'A256GCM',
status: 'active',
k: 'uF5t2qLGVMrT8zmQhC9aH0-7pQWBIJEPVwNXAXEuwpA', // example key only
},
],
};
const activeKey = keyset.keys.find((key) => key.status === 'active');
const cek = base64url.decode(activeKey.k); // 32-byte AES-256-GCM key (alg: dir)
// 2. Build the payload at click time.
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: 'acme-care-platform',
aud: 'charthero',
iat: now,
exp: now + 300, // at most five minutes
jti: randomUUID(), // unique per launch
launch_state: { appointment_id: 'apt-789', correlation_id: 'partner-click-123' },
actor: { email: 'clinician@example.com' }, // optional
};
// 3. Encrypt as a compact JWE. The protected header must be exactly these four keys.
const ctx = await new CompactEncrypt(
new TextEncoder().encode(JSON.stringify(payload)),
)
.setProtectedHeader({
alg: 'dir',
enc: 'A256GCM',
kid: activeKey.kid,
typ: 'charthero-org-integration-launch+jwe',
})
.encrypt(cek);
// 4. Redirect the browser to the launch URL.
const url = `https://app.charthero.ai/launch/acme-care-prod?ctx=${ctx}`;
ChartHero decrypts ctx with the matching kid from your keyset, validates the
claims, and calls your launch-context endpoint. Mint a fresh link per click — links
expire in five minutes and are single-use.
Implement the callback ChartHero calls after decrypting ctx.
Review exact patient, appointment, and actor IDs.
Validate expired, replayed, malformed, and successful launches.