Integrating FittyAI Virtual Trainer
The Full FittyAI workout integration lets you drop a fully‑featured virtual trainer into any React (or React Native WebView) app in minutes. All that’s required is an <iframe>
and a handful of event listeners.
1 · Prerequisites
Credential | How to obtain |
---|---|
clientId | Provided during onboarding |
secret | Provided during onboarding (used to create a JWT token) |
To get
workoutId
either fetch possible workouts using Workouts API or store enabled for your client workouts in your storage.
2 · Project Setup
yarn create react-app fitty‑demo
cd fitty‑demo
yarn add uuid # generate sessionId
Create .env
at the project root:
REACT_APP_FITTY_ENV=prod
REACT_APP_FITTY_CLIENT=<clientId>
3 · Acquire JWT for User Authorization
⚠️ Important: Acquiring the JWT must be managed on your backend server. It’s essential because this process involves sending your secret. For security, never store or send secrets from a client device.
Read the instructions to receive your JWT here.
Upon retrieval, the JWT will resemble:
const userToken = "eyJhbGciOiJIUzI1NiIsInR5cCI...";
4 · Generate session id
import { v4 as uuidv4 } from "uuid";
const sessionId = uuidv4();
5 · Embedding the Iframe
Below is the complete minimal component—no extras, no optional query parameters—so the integration looks as simple as it is:
import React, { useEffect, useRef } from "react";
export default function FittyWorkout() {
const iframeRef = useRef(null);
// Keep the iframe 100 vh
useEffect(() => {
const resize = () => {
if (iframeRef.current) iframeRef.current.style.height = window.innerHeight + "px";
};
window.addEventListener("resize", resize);
resize();
return () => window.removeEventListener("resize", resize);
}, []);
/**
* Listen for FittyAI Event Bridge messages
*/
useEffect(() => {
const allowed = `https://${process.env.REACT_APP_FITTY_ENV}.fittyai.com`;
const onMessage = (e) => {
if (e.origin !== allowed) return;
let data = typeof e.data === "string" ? JSON.parse(e.data || "{}") : e.data;
switch (data.type) {
case "initiationCompleted":
// you can use this even to hide your custom spinner while iframe is being loaded
break;
case "indexUpdated":
// data.index holds the 0‑based exercise index
// you can store user's progress in the workout so that next time you load the workout from the exercise
// that the user ended by adding ?eventIdex={exerciseIndex} where exerciseIndex came from the previous session
// indexUpdate event
break;
case "closeRequested":
// this event is sent when user presses close button in the workout
break;
case "sessionEnd":
// after workout session is completed use our sessions API endpoint to fetch user's results here (see section 7)
break;
default:
break;
}
};
window.addEventListener("message", onMessage);
return () => window.removeEventListener("message", onMessage);
}, []);
const url = `https://${process.env.REACT_APP_FITTY_ENV}.fittyai.com/web/${process.env.REACT_APP_FITTY_CLIENT}/${process.env.REACT_APP_WORKOUT_ID}/${sessionId}?JWT=${userToken}`;
return <iframe ref={iframeRef}
id="fittyai"
src={url}
width="100%"
height="100%"
style=
allow="camera" />;
}
That’s it—the workout runs full‑screen, counts reps and talks to the user. Everything else is optional.
6 · Event Bridge
During the session the iframe posts JSON messages to its parent window:
Event | Payload | When |
---|---|---|
initiationCompleted |
{ type: "initiationCompleted" } |
After internal bootstrap |
indexUpdated |
{ type: "indexUpdated", index: number } |
Whenever the active exercise changes |
closeRequested |
{ type: "closeRequested" } |
User hit X and the URL contained suppressFinish=true |
sessionEnd |
{ type: "sessionEnd" } |
Workout finished or host called nativeBridge.close() |
Why sessionEnd? Internally the player calls
window.parent.postMessage("sessionEnd", "*")
when the workout is over. Treat it as the final “all done”.
7 · Fetching the Results
After you receive sessionEnd
(or decide to treat closeRequested
as the end) call the Get Session endpoint to pull the full workout analytics:
GET /v1/{clientName}/sessions/{sessionId}
Path parameter | Description |
---|---|
clientName |
Your clientId (set in REACT_APP_FITTY_CLIENT ) |
sessionId |
The UUID you generated for this workout |
Example fetch (browser)
const { REACT_APP_FITTY_CLIENT: clientName, REACT_APP_JWT: jwt } = process.env;
if (data.type === "sessionEnd") {
fetch(`/v1/${clientName}/sessions/${sessionId}`, {
headers: { Authorization: `Bearer ${jwt}` }
})
.then(r => r.json())
.then(console.log);
}
Successful response
{
"kcal": 98,
"score": 23660,
"avg_accuracy": 76,
"exercises": [
{
"name": "jumping_jack",
"reps": 50,
"nr_mistakes": 15,
"avg_accuracy": 79,
"all_mistakes": [
"stretch your arms out more",
"make sure to stretch your arms out more"
]
}
]
}
See the full schema and error codes in the Get Session docs.
8 · Optional URL Query Parameters
Add these only when you need them—they all work independently.
Parameter | Purpose | Example |
---|---|---|
eventIndex |
Load the workout starting from a specific exercise | ?eventIndex=5 |
suppressFinish |
Use suppressFinish=true to disable our workout summary screen and show your custom summary screen; emits closeRequested instead |
?suppressFinish=true |
Simply append one or more to the iframe URL (order doesn’t matter).
9 · Security Checklist
- Origin check – Always compare
event.origin
withhttps://<env>.fittyai.com
before trusting data. - Permissions Policy – Keep
allow="camera"
on the iframe.