# 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 ```ts 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 `ETag` only 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 `304` extends 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 `ETag` polling; synthetic `catalog.updated` tests never invalidate a live cache. - At the exact HTTP or publication expiry, hide affected cards, purge their mirrors, and clear the conditional `ETag` before 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: 1. Call `downloadAndVerifyCatalogAsset` with the runtime key and catalog `sha256`. 2. Stream into a new temporary object/file; cap bytes before buffering or decoding. 3. Let the SDK verify origin/path, status, content type, content length, `ETag`, `Content-Digest`, `nosniff`, absence of forbidden headers, actual length, and SHA-256. 4. Commit only after full verification. On any error, abort and delete the temporary object. 5. Publish at a new random partner-owned path with no catalog/program/condition/user/asset identifier. 6. 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`](https://partners.chia.health/source/reference-backend/server.ts.txt). 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_action` field visible before the CTA; - never truncate a field because `may_truncate` is 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. ```ts 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 calls `POST /v2/handoff-sessions` using `item.id` and `primary.disclosureVersion` - `Continue 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`: ```json { "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/`; production returns `livemode:true` at `${CHIA_API_ORIGIN}/production/handoff/`. 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](https://partners.chia.health/docs/mobile/). 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 ` × 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.