One signal at completion.
Your platform grants the reward.
Every RheumaView interactive module ends the same way: when a player finishes, the module hands a compact completion signal — score, points, achievements, tier — to the page hosting it. Your platform or the sponsor’s redemption page reads that signal and decides what to grant. The mechanism is identical across the entire portfolio.
How the reward hand-off works
Three steps, fully client-to-client. The module never touches your fulfilment system — it emits a signal; your side does the crediting.
Completion → Signal → Reward
Continuous loop shown for illustration · one real hand-off fires per attempt
tier C · 🏅 4
Watch the full walkthrough
Part A · points accumulate → Part B · hand-off to the sponsor
The same hand-off powers the whole portfolio
You integrate once. The completion signal is structurally identical whether the player just finished a Crucible diagnostic case, a Cascade therapeutic-education module, a RheumaView Simulator, or any future interactive module we ship. Tap any product to view it live.
Crucible ↗
Diagnostic case challenges. Emits a completion event and a reward-eligibility event when the score clears the configured threshold.
📈Cascade ↗
Therapeutic-education walkthroughs. Hands off score, points, streak, achievements and tier — via event and/or URL parameters to a sponsor redemption page.
🧪RheumaView Simulators ↗
Interactive clinical simulations. Same completion signal, same listener — no new integration on your side.
✨Every other module ↗
Anything interactive we build adopts the shared hand-off contract, so your reward pipeline keeps working unchanged.
The technical detail, when you need it
Expand any section below. Everything here is configured per build — the snippets show the shape of the contract, not fixed values.
BoundariesWhat the hand-off is — and is not
In short: the module is the source of a signal, not a fulfilment engine. Identity, rate-limiting, anti-abuse, tax/compliance reporting, and ledger integrity all live on your side.
The module does
- Emit a one-time completion signal with the score and effort metrics
- Signal reward-eligibility when a configured threshold is met
- Pass through an opaque reward label or score parameters you defined at build time
- Include calibration / timing / achievement summaries when those features are enabled
The module does not
- Identify the player (no user ID, no email, no session)
- Call your API, webhook, or fulfilment endpoint
- Issue, track, deduplicate, or report rewards
- Validate the score server-side or prevent client-side tampering
The signalTwo ways the signal reaches you
Depending on the module and your setup, the completion signal arrives by one or both of these channels. Both carry the same result data.
A · postMessage event (iframe → your page)
The module sends a window.parent.postMessage(...) when the player finishes. Fires
once per attempt; a fresh attempt fires again with new values.
// completion — fires on success and on the fail screen { type: "module:case-complete", caseId: "CSC-001", product: "crucible" | "cascade" | "simulator", tier: "A" | "B" | "C", success: true, // false on fail screen score: 52, maxScore: 60, percent: 71, points: 587, streakMax: 5, achievements: 4, timestamp: 1730000000000 }
A second module:reward-eligible event fires immediately after, in the same tick,
only when reward trigger is enabled, the attempt succeeded, and the threshold
was met. It carries an opaque rewardLabel you map back to your reward catalog.
B · URL parameters (module → sponsor redemption page)
Used by Cascade-style reward redemption: the player clicks “Claim my reward” and the module opens the sponsor’s page with the result appended as query parameters. The sponsor’s page reads them and decides what to grant.
https://sponsor.example.com/reward/?caseId=CSC-001&drugName=TYK2i-DEMO &points=587&percent=71&streakMax=5 &achievements=4&tier=C&totalAttempts=12
IntegrationListener template
Paste this on the page where you embed the module. One listener handles every product.
<iframe id="rv-module" src="https://www.rheumaview.com/m/CSC-001/" width="900" height="940" style="border:0;max-width:100%;" loading="lazy"></iframe> <script> window.addEventListener("message", (e) => { // 1 · origin check — required if (e.origin !== "https://www.rheumaview.com") return; if (!e.data || typeof e.data !== "object") return; // 2 · reward-eligible — credit the player using YOUR session if (e.data.type === "module:reward-eligible") { fetch("/api/rv/credit", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ caseId: e.data.caseId, score: e.data.score, rewardLabel: e.data.rewardLabel }) }); return; } // 3 · case-complete — analytics, completion tracking, leaderboards if (e.data.type === "module:case-complete") { yourAnalytics.track("rv_complete", e.data); } }); </script>
e.origin. Without it, any other
page could spoof an event. The origin is always https://www.rheumaview.com.Who is the playerIdentifying the player on your side
The module has no idea who the player is. You do.
Pattern A — your own session (recommended)
Your page already requires login, so when your listener runs, the browser carries your authenticated session. Your credit endpoint reads it, looks up the user, and credits them. The module never participates in identity.
Pattern B — echo a token through the module
If your session cookie is not available in the module’s context, generate a short-lived opaque
token, pass it via the URL (?uid=opaque_token), and the module echoes it back as
echoToken in the payload. Your endpoint maps the token to the user.
Trust modelAnti-tampering and reward classes
| Reward type | Signal alone OK? | Server-side validation |
|---|---|---|
| Internal platform points / XP | Yes | No |
| Badges, progress, leaderboards | Yes | No |
| Coupons / discount codes < $5 | Yes (rate-limit) | Recommended |
| Gift cards ≥ $5 | No | Required |
| CME credit | No | Required + audit log |
| Cash honoraria | No | Required + KYC |
For high-value rewards your fulfilment endpoint must: require an authenticated session; be
idempotent on (userId, caseId); rate-limit per user per window; and log every
attempt, accepted or rejected, for audit.
ResponsibilitiesCompliance is the buyer’s surface
The module emits a technical signal and collects no PII. The completion and reward-eligible payloads contain no user-identifying fields. Linking a real person to an attempt happens entirely on your platform, under your privacy notice and legal basis.
| Regime | Buyer responsibility |
|---|---|
| PhRMA Code (US) | Determine HCP status; apply gift / educational-item rules. |
| Sunshine Act (US) | Report transfers of value above threshold to the registry. |
| 1099 reporting (US) | Aggregate per-recipient annual payments > $600/year. |
| ACCME independence | Separate reward issuance from the accreditation pathway. |
| GDPR / UK GDPR | Lawful basis & privacy notice for storing “X completed Y”. |
| Local tax on gift cards | Apply local rules (EU, US state-level, etc.). |
Before go-liveHow to test
- Embed the module on a staging page and open the DevTools console.
- Add the listener with extra
console.log()calls. - Pass the module to a clearing score — confirm
case-complete(success:true) thenreward-eligiblewith the expected label. - Re-run below threshold —
case-completefires,reward-eligibledoes not. - Trigger the fail screen —
case-completefires withsuccess:false; no eligibility. - Curl your credit endpoint with no session — confirm it rejects the request.
- Hit it twice with the same
(userId, caseId)— confirm the duplicate is rejected.