Catalog, assets, and handoff
The catalog is a small set of immutable, approved publications. Fetch it from a scheduled job in your backend, turn it into a complete verified local snapshot, then serve that snapshot to mobile. A user view must never cause a Chia API or asset request.
Refresh without blocking users#
const result = await runtime.listCatalog(
currentEtag ? { ifNoneMatch: currentEtag } : {},
);
if (!result.notModified) {
// Download and verify every asset into temporary storage first.
// Atomically publish the whole snapshot only after all items succeed.
await publishCompleteSnapshot(result.data, result.etag);
}
- Save the strong
ETagonly with its complete catalog snapshot. - Start one representation-freshness deadline no later than the request start (or, in a production cache, at exact response-header receipt) and honor the returned exact private
Cache-Control: max-age; transport and retry time may shorten but must never extend it. The response maximum is never greater than five minutes or the earliest publication expiry. - A
304extends only the representation cache deadline. It never extends a publication's own validity or an asset's authorization. - Coalesce concurrent refreshes. On a retryable failure, keep only the last complete snapshot while both its HTTP freshness deadline and every displayed publication validity remain active. There is no stale-on-error window.
- V2 emits no live catalog webhook. Refresh on the catalog response's cache deadline and use conditional
ETagpolling; syntheticcatalog.updatedtests never invalidate a live cache. - At the exact HTTP or publication expiry, hide affected cards, purge their mirrors, and clear the conditional
ETagbefore the next fetch.
This design keeps catalog browsing fast during Chia latency or a transient outage: users read the partner's local snapshot. Only the affirmative share action makes a small synchronous session-creation call.
Mirror assets safely#
presentation.image.backend_download_url is authenticated, server-only, non-CORS, and no-store. Never expose it to mobile. Mirror and publish only items whose availability.status is available. An unavailable item replaces any prior version of that item, but its authenticated asset URL must not be fetched. For each available item in a candidate snapshot:
- Call
downloadAndVerifyCatalogAssetwith the runtime key and catalogsha256. - Stream into a new temporary object/file; cap bytes before buffering or decoding.
- Let the SDK verify origin/path, status, content type, content length,
ETag,Content-Digest,nosniff, absence of forbidden headers, actual length, and SHA-256. - Commit only after full verification. On any error, abort and delete the temporary object.
- Publish at a new random partner-owned path with no catalog/program/condition/user/asset identifier.
- Swap the entire catalog snapshot atomically, then purge old mirrors.
The full commit/abort sink is implemented in examples/partner-reference-backend/src/server.ts. Do not replace it with fetch(url).arrayBuffer() or write directly over the live object.
Render the approved publication#
The signed bundle includes the official dependency-free web reference at examples/partner-reference-backend/src/renderer-assets.ts and its closed projection helper at renderer-contract.ts. Run the sandbox reference and open http://localhost:8787/demo to inspect it. The helper removes the authenticated asset URL, deep-freezes the projected release, records the catalog version, presentation digest, disclosure version, resolution time, and earliest freshness boundary, then returns either one complete visible decision or null. It has no network, storage, logging, callback, or analytics surface.
Treat one item as an indivisible presentation contract:
- preserve
presentation_contract.block_order; - make every
visible_before_actionfield visible before the CTA; - never truncate a field because
may_truncateis empty; - display the advertising label, title, summary, complete price scope/terms/minimum, parties and roles, disclosures, and exact CTA;
- use only the verified partner mirror and supplied accessible alt text;
- do not add drug guarantees, eligibility claims, discounts, inventory, rankings, or rewritten seller/provider roles.
Apply the item's exact local display policy. Fresh, authenticated partner account service-state evidence in an approved state allows the card. Missing, stale, future-dated, invalid, or unsupported evidence normally hides it; a publication whose unknown_or_stale_behavior is exactly show_with_chia_eligibility_disclosure may remain visible only with every supplied disclosure and a neutral_only Chia route. That exception never authorizes sharing the selection. Never send the evidence to Chia.
const decision = resolveCatalogHandoff(item, {
stateCode: account.serviceState,
evidenceBasis: "partner_account_service_state",
observedAt: account.serviceStateObservedAt,
});
Resolve again at tap time. Do not keep a prior share_or_neutral decision after its evidence or publication expires.
On browser pageshow and foreground restore, refresh the projection without inferring whether the user completed anything in Chia. A changed, missing, or expired release closes the prior choice and returns the user to the current catalog. An affirmative share keeps one request body and one random UUIDv4 while the outcome is uncertain; concurrent taps join the same in-flight work. A definitive client rejection discards that attempt and forces a fresh projection. An ambiguous network error, 429, or server failure retries the same bytes and key. Never persist the returned handoff URL.
Just-in-time choice#
Do not call Chia or load a destination before the user taps the catalog CTA and makes the JIT choice.
For share_or_neutral, show the supplied heading/body, recipient, exact data_shared, purpose, compensation disclosure, and all policy links. Present both actions with equal access:
Share selection and continue→ your backend callsPOST /v2/handoff-sessionsusingitem.idandprimary.disclosureVersionContinue without sharing→secondary.url
The policy links are versioned contract data, not developer-configured defaults. Render the exact partner_consumer_health_policy_url and partner_privacy_rights_url returned with that publication; do not replace them with the partner's general privacy-policy URL. Suppress ad/analytics/session-replay SDK events across the catalog, selection, JIT-choice, and outbound-handoff surfaces so treatment interest is not disclosed to advertising or profiling systems.
For neutral_only, suppress session creation entirely and show only the supplied neutral heading/body and:
Continue to Chia→url
The disclosure is inline beside these existing actions. Do not add a checkbox, modal, or preload. Both share choices use the same action size and remain keyboard and screen-reader reachable; visual emphasis may differ only through the standard primary/secondary styling, not placement, availability, or extra steps.
After affirmative choice, your backend sends exactly this closed body and a caller-persisted random UUIDv4 Idempotency-Key:
{
"catalog_item_id": "cat_…",
"disclosure_version": "hnd_…",
"consent_attestation": {
"affirmed": true,
"affirmed_at": "2026-07-22T15:00:00Z"
}
}
The share path tells Chia only the selected care program, the credential-bound partner referral origin, exact approved disclosure, and fact/time of affirmative choice. It sends no name, partner account/user ID, email, phone, device ID, answers, state evidence, arbitrary metadata, patient/order reference, or IP/user-agent field in the JSON body. Unknown fields are rejected. The neutral path contains no partner/program context and asks the user to choose again inside Chia.
The 201 response is exactly {object,id,url,expires_at,livemode} and has no test field. Sandbox returns livemode:false at ${CHIA_API_ORIGIN}/sandbox/handoff/<phs id>; production returns livemode:true at ${CHIA_API_ORIGIN}/production/handoff/<phs id>. Both expire after 15 minutes. There is no GET/status endpoint and no patient/order webhook. Retry only with the same key and byte-identical body.
Open exactly once in the OS browser#
Pass the chosen URL directly to the platform browser API from the explicit tap handler. Use the mobile snippets.
Never use:
- an embedded WebView or JavaScript bridge;
fetch,HEAD, preconnect, preview, prefetch, or link expansion;- redirectors, shorteners, URL rewriting, custom headers, or query parameters;
- persistence, clipboard instrumentation, crash breadcrumbs, logs, or analytics containing the URL;
- a cookie or previous browser state to infer partner attribution.
The phs_… URL is opaque, short-lived routing capability—not an authentication credential, user identifier, or analytics token. Never log, persist, preview, rewrite, or reuse it after expiry. Missing, invalid, expired, superseded, withdrawn, revoked, or neutral context remains generic Chia.
Boundary after handoff#
The partner receives no callback or person-level status. Do not infer purchase from browser return/app focus, reconcile by user, or advertise prescription/fulfillment certainty. A separately approved generic return navigates to the partner's marketplace home and ignores its entire query/fragment.
Chia alone handles identity, intake, eligibility, clinical consent, checkout, payment, merchant-of-record duties, clinical care, prescription, cancellation/refund, pharmacy, shipping, and patient support. The sandbox handoff opens a synthetic page. Before written activation, the production handoff fails closed. After written activation, it opens the real, credential-bound <Partner> × Chia flow, removes the handoff capability from the visible URL, disables advertising/behavioral tracking in partner context, and keeps all patient and commerce records inside Chia. Co-brand copy and partner-visible configuration are resolved exclusively from the authenticated tenant; a caller cannot select or override another partner's brand in the request.