How it works
Three steps
The whole flow, from token minting to prediction display.
Mint a token
Your backend exchanges your API key for a short-lived JWT bound to the player identity.
Embed the iframe
Drop our iframe on the page. When it signals `ready`, send an `init` message with the token and the payload.
Receive the result
The iframe computes, renders the risk gauge, and emits a `result` postMessage you can log or forward.
Quick start
Two snippets
A tiny backend endpoint, then the iframe embed on your page.
1. Backend — mint a token
Your API key must live on the server, never in your frontend bundle. Expose a small proxy endpoint that your frontend can call:
// POST /api/mytwin-token — called by your frontend
import express from "express";
const app = express();
app.post("/api/mytwin-token", async (req, res) => {
const { playerId } = req.body;
const response = await fetch("https://api.my-twin.io/v1/iframe/token", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.MYTWIN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ externalId: playerId }),
});
const { token } = await response.json();
res.json({ token });
});2. Frontend — embed the iframe
Drop this snippet wherever you want the module to appear. The iframe auto-resizes to fit its content.
<div id="mytwin-injury"></div>
<script>
const iframe = document.createElement("iframe");
iframe.src = "https://athletes-iframe.my-twin.io/v1?lang=en";
iframe.style.width = "100%";
iframe.style.border = "0";
document.getElementById("mytwin-injury").appendChild(iframe);
window.addEventListener("message", async (e) => {
if (e.origin !== "https://athletes-iframe.my-twin.io") return;
if (e.data.type === "ready") {
const { token } = await fetch("/api/mytwin-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ playerId: "current-user-id" }),
}).then((r) => r.json());
iframe.contentWindow.postMessage({
type: "init",
token,
user: {
playMinutesLast7Days: 420,
playMinutesLast28Days: 1500,
lastMatchDate: "2026-04-18",
nextMatchDate: "2026-04-25",
},
}, "https://athletes-iframe.my-twin.io");
}
if (e.data.type === "resize") {
iframe.style.height = e.data.height + "px";
}
if (e.data.type === "result") {
console.log("Prediction:", e.data.result);
// { riskScore: 62, riskLevel: "moderate", recommendation: "..." }
}
if (e.data.type === "error") {
console.error("MyTwin error:", e.data.code, e.data.message);
}
});
</script>That's the whole integration. See it live on our playground.
@mytwin/injury-sdk will reduce this to ~10 lines by handling the postMessage handshake, origin validation and auto-resize for you.Reference
postMessage protocol
Always validate event.origin before trusting a message.
Parent → iframe
| Type | Fields | When |
|---|---|---|
| init | token, user | After receiving ready |
Iframe → parent
| Type | Fields | Description |
|---|---|---|
| ready | — | Iframe loaded, waiting for init |
| result | result | Prediction computed successfully |
| error | code, message | Token, payload or backend failure |
| resize | height | Content height changed (apply to iframe.style.height) |
| leave | — | User dismissed the module |
Reference
Payload types
The exact shape of init.user and the result you get back.
// Parent → iframe
type InitMessage = {
type: "init";
token: string; // short-lived JWT, ~30 min TTL
user: PredictionInput;
};
type PredictionInput = {
age?: number; // optional, 5-120
playMinutesLast7Days: number; // 0-10080
playMinutesLast28Days: number; // 0-40320
lastMatchDate: string; // "YYYY-MM-DD"
nextMatchDate: string; // "YYYY-MM-DD"
};
// Iframe → parent
type PredictionResult = {
riskScore: number; // 0-100
riskLevel: "low" | "moderate" | "high";
recommendation: string; // short localized text
};All fields in PredictionInput are required except age. In practice, a padel/tennis CRM derives playMinutesLast*Days from the booking history, and lastMatchDate / nextMatchDate from the booking table itself.
Reference
Error codes
The iframe emits an error message when something goes wrong. Handle each code appropriately on your side.
| Code | Cause | Recommended action |
|---|---|---|
| TOKEN_EXPIRED | JWT past its exp | Mint a new token, re-send init |
| TOKEN_INVALID | JWT signature or payload rejected | Check your API key and clock skew |
| VALIDATION_ERROR | user payload missing required fields | Fix the payload, re-send init |
| BACKEND_DOWN | MyTwin prediction service unavailable | Show a retry UI on your side |
| UNKNOWN | Unexpected failure | Report to support with the message field |
Data & privacy
Stateless by design
How player data flows through the module and what that means for your GDPR posture.
- No storage. The iframe forwards your payload to our prediction service, returns the result, and discards both. Nothing is persisted on our side.
- Processor role. MyTwin acts as a pure data processor (GDPR Art. 28). A DPA is signed with each partner at contract time.
- Not special-category data. The inputs (age, play minutes, match dates) are wellness / activity data, not GDPR Art. 9 health data. No explicit consent screen is required inside the iframe — your usual legal basis (contract or legitimate interest) covers the processing.
- Consent is your call. Any user consent needed under your own legal basis must be collected on your side, before opening the iframe — by then the payload is already in transit.
FAQ
Common questions
- Can I test on localhost?
- Yes. Our staging CSP allows embedding from any origin. In production we lock frame-ancestors to the domains on your contract.
- What languages are supported?
- French (default) and English. Pass ?lang=fr or ?lang=en in the iframe URL. Unsupported languages silently fall back to French.
- How long does a JWT live?
- Currently 30 minutes. On TOKEN_EXPIRED, mint a fresh one from your backend and re-send an init message.
- How do I get an API key?
- Contact contact@my-twin.io. We'll handle the contract, DPA, and provision your key.
- Is there a backend SDK?
- Not yet. The token endpoint is a plain POST, so any HTTP client works. A @mytwin/injury-server SDK (Node, Python, PHP) is planned once we have ≥3 partners live.
Ready to integrate?
Get your API key and start embedding.