Aller au contenu
Meletia

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 :

URL de base
https://api.meletia.fr/v1

Authentification

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

JavaScript (serveur)
// 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.

Réponse
{
  "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.

Réponse
{
  "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

CodeerrorCause
400invalid_inputParamètre invalide (identifiant mal formé, période autre que 7, 30 ou 90)
401invalid_api_keyClé absente, mal formée ou révoquée
404not_foundQuiz inexistant ou appartenant à un autre compte
429rate_limitedPlus 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

JSON
{
  "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 : true pour un envoi d'essai depuis le tableau de bord.
  • completed : false si le joueur a quitté la page avant la fin.
  • answers : une entrée par question tirée. reached vaut false pour une question pas atteinte (temps écoulé, plus de vie). answer est la réponse du joueur au format de la question, ou null.
  • 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.

TypeScript (Next.js)
// 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.

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