Référence de l'API et des webhooks
Points d'accès, authentification, format des webhooks et événement du widget, pour les développeurs.
API publique
L'API donne accès en lecture à vos quiz et à leurs statistiques, depuis votre serveur. Elle répond en JSON, à l'adresse :
https://api.meletia.fr/v1Authentification
Créez une clé dans API et webhooks (10 clés au plus par compte). La clé n'est affichée qu'une fois ; Meletia n'en garde qu'une empreinte. Envoyez-la dans l'en-tête Authorization de chaque requête. Chaque clé donne accès à tous les quiz du compte.
curl https://api.meletia.fr/v1/quizzes \
-H "Authorization: Bearer mk_…"La clé est secrète : utilisez-la seulement depuis un serveur. L'API n'envoie pas d'en-têtes CORS, un navigateur ne peut donc pas l'appeler depuis une page.
// Sur votre serveur uniquement : la clé ne doit jamais aller dans le navigateur.
const res = await fetch("https://api.meletia.fr/v1/quizzes/8f3c2a71-0000-4000-8000-000000000000/stats?days=30", {
headers: { Authorization: `Bearer ${process.env.MELETIA_API_KEY}` },
});
const stats = await res.json();
console.log(stats.totals); // { starts, completions, averageScore }GET /v1/quizzes
Vos quiz, brouillons compris, du plus récent au plus ancien. publishedAt vaut null pour un brouillon.
{
"quizzes": [
{
"id": "8f3c2a71-0000-4000-8000-000000000000",
"title": "Capitales du monde",
"description": null,
"locale": "fr",
"distribution": "embed",
"resultsStorage": "hosted",
"publishedAt": "2026-10-20T09:12:00.000Z",
"createdAt": "2026-10-18T16:40:00.000Z",
"updatedAt": "2026-10-20T09:12:00.000Z",
"questionCount": 10
}
]
}GET /v1/quizzes/:id
Un quiz avec ses réglages, ses sites autorisés (allowedDomains) et ses questions dans l'ordre, solutions comprises. Chaque question a un type (mcq, truefalse, text, list, order, match, cloze, poll) et le contenu propre à son format.
GET /v1/quizzes/:id/stats?days=30
Les chiffres de l'onglet Résultats, sur 7, 30 ou 90 jours (UTC, aujourd'hui compris) : totaux, une ligne par jour, et une ligne par question. Les scores sont des proportions entre 0 et 1 ; null quand il n'y a pas encore de partie terminée. Pour un sondage, successRate vaut null et votes donne les votes par choix.
{
"days": 30,
"since": "2026-09-30",
"totals": { "starts": 412, "completions": 358, "averageScore": 0.71 },
"daily": [
{ "day": "2026-09-30", "starts": 12, "completions": 10, "averageScore": 0.68 }
],
"questions": [
{
"id": "dc386adb-…",
"position": 0,
"type": "mcq",
"prompt": "Quelle est la capitale de l'Australie ?",
"answered": 355,
"correct": 201,
"successRate": 0.566,
"votes": null
}
]
}Erreurs et limites
| Code | error | Cause |
|---|---|---|
| 400 | invalid_input | Paramètre invalide (identifiant mal formé, période autre que 7, 30 ou 90) |
| 401 | invalid_api_key | Clé absente, mal formée ou révoquée |
| 404 | not_found | Quiz inexistant ou appartenant à un autre compte |
| 429 | rate_limited | Plus de 60 requêtes par minute avec la même clé |
Webhooks
Pour un quiz dont le stockage des résultats est « Envoyés à mon serveur (webhook) », chaque partie, terminée ou abandonnée, est envoyée en POST à l'adresse de webhook de votre compte (une par compte, en https://), quelques secondes après la fin. Meletia ne garde que les totaux par jour. Guide pas à pas : Récupérer les résultats.
En-têtes
Meletia-Event:game.finished.Meletia-Delivery: identifiant unique de l'envoi, identique à chaque nouvelle tentative. Ignorez un identifiant déjà reçu.Meletia-Signature:t=<horodatage Unix>,v1=<signature>.
Contenu
{
"id": "9b2f6c1e-…",
"type": "game.finished",
"createdAt": "2026-10-29T13:15:21.655Z",
"test": false,
"data": {
"quiz": { "id": "8f3c2a71-0000-4000-8000-000000000000", "title": "Capitales du monde" },
"completed": true,
"points": 7,
"maxPoints": 10,
"answers": [
{
"questionId": "dc386adb-…",
"position": 0,
"type": "text",
"reached": true,
"correct": true,
"points": 1,
"maxPoints": 1,
"answer": { "text": "Paris" }
}
]
}
}test:truepour un envoi d'essai depuis le tableau de bord.completed:falsesi le joueur a quitté la page avant la fin.answers: une entrée par question tirée.reachedvautfalsepour une question pas atteinte (temps écoulé, plus de vie).answerest la réponse du joueur au format de la question, ounull.- Aucun identifiant de joueur ni adresse IP.
Vérifier la signature
Calculez un HMAC SHA-256, en hexadécimal, de t.corps (l'horodatage, un point, puis le corps brut de la requête, avant tout décodage) avec le secret affiché dans API et webhooks. Comparez-le à v1 en temps constant, et refusez les envois de plus de 5 minutes.
// app/api/meletia-webhook/route.ts
import crypto from "node:crypto";
export async function POST(request: Request) {
const body = await request.text();
const header = request.headers.get("meletia-signature") ?? "";
const t = header.match(/t=(\d+)/)?.[1];
const v1 = header.match(/v1=([0-9a-f]{64})/)?.[1];
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) {
return new Response("Invalid signature", { status: 401 });
}
const expected = crypto
.createHmac("sha256", process.env.MELETIA_WEBHOOK_SECRET!)
.update(`${t}.${body}`)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(body);
if (!event.test) {
// Enregistrez ici event.data dans votre base (en ignorant un event.id déjà reçu).
await saveGame(event.id, event.data);
}
return new Response("ok");
}Réponse attendue et nouvelles tentatives
Répondez par un code 2xx en moins de 5 secondes. Sans réponse, ou avec un code 5xx ou 429, Meletia réessaie deux fois, après 2 puis 8 secondes. Les redirections ne sont pas suivies. Les parties au-delà du quota mensuel du plan ne sont pas envoyées.
Événement du widget
Quand le script embed.js est présent dans la page, il déclenche l'événement meletia:complete sur l'iframe à la fin de chaque partie ; il remonte jusqu'à window. event.detail contient quizId, quizTitle, points, maxPoints, percent (arrondi) et reason (done, time ou lives). Le score est calculé dans le navigateur du joueur : il peut être modifié, utilisez le webhook pour un résultat qui fait foi.
<script>
window.addEventListener("meletia:complete", function (event) {
var result = event.detail;
// result.quizId, result.quizTitle, result.points, result.maxPoints,
// result.percent, result.reason ("done", "time" ou "lives")
console.log("Quiz terminé :", result.percent + " %");
});
</script>