v1 · beta

Integration Guide

Embed injury risk predictions directly in your booking or CRM flow. Stateless, JWT-secured, GDPR-friendly — and integratable in under 30 lines of code.

How it works

Three steps

The whole flow, from token minting to prediction display.

1

Mint a token

Your backend exchanges your API key for a short-lived JWT bound to the player identity.

2

Embed the iframe

Drop our iframe on the page. When it signals `ready`, send an `init` message with the token and the payload.

3

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:

Node.js
// 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.

HTML
<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.

SDK coming in v1.1 — @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

TypeFieldsWhen
inittoken, userAfter receiving ready

Iframe → parent

TypeFieldsDescription
readyIframe loaded, waiting for init
resultresultPrediction computed successfully
errorcode, messageToken, payload or backend failure
resizeheightContent height changed (apply to iframe.style.height)
leaveUser dismissed the module

Reference

Payload types

The exact shape of init.user and the result you get back.

TypeScript
// 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.

CodeCauseRecommended action
TOKEN_EXPIREDJWT past its expMint a new token, re-send init
TOKEN_INVALIDJWT signature or payload rejectedCheck your API key and clock skew
VALIDATION_ERRORuser payload missing required fieldsFix the payload, re-send init
BACKEND_DOWNMyTwin prediction service unavailableShow a retry UI on your side
UNKNOWNUnexpected failureReport 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.

Contact sales