Spring til indhold
Integrationer til udviklere

Kobl Puzzel til din egen platform

Puzzel kan sende en spillers resultat direkte videre til et andet system — deres score, hvor langt de kom, hvad de svarede — uden at det system behøver at være et helt LMS. Denne side er alle kanalerne, der findes: hvad der sendes ud, hvornår det sendes ud, og det mindste, du selv skal bygge for at tage imod det.

Resultater sendes ud via
En webhook, eller en besked til siden omkring aktiviteten
Din side kan
Logge spilleren ind og nulstille aktiviteten
Slås til
Pr. aktivitet, under Udvikler i editoren
Inkluderet med
En betalt plan — disse indstillinger er slået fra på en gratis konto

Hvilken kanal har du brug for?

Tre ting kan forlade en aktivitet, og én kan komme ind. Hvilken der passer, afhænger af ét spørgsmål: sidder spilleren inde i din side, eller et helt andet sted?

Ud af Puzzel
Ind i Puzzel

Resultatwebhook

Slå "Send resultater til en webhook" til i editoren, og giv den en URL. Fra da af sender spillerens browser hele resultatet som JSON med POST til den URL, hver gang deres fremskridt gemmes.

Sådan sætter du det op
  1. 1 Åbn aktiviteten i editoren, og gå til menuen Udvikler.
  2. 2 Slå "Send resultater til en webhook" til, og indsæt dit endpoint i feltet nedenunder. Det skal være en fuld URL — et rent domæne bliver afvist — og det skal være https, fordi browseren blokerer et almindeligt http-kald fra en side, der serveres over https.
  3. 3 Spil aktiviteten selv én gang. Det første POST-kald lander, så snart du svarer på noget.
En modtager, fra ende til anden
import express from 'express';

const app = express();

// The POST is made by the player's browser, so the browser asks permission
// first. Answer the preflight and the real request can land.
app.use((req, res, next) => {
  res.set('Access-Control-Allow-Origin', 'https://puzzel.org');
  res.set('Access-Control-Allow-Headers', 'Content-Type');
  if (req.method === 'OPTIONS') return res.sendStatus(204);
  next();
});

app.post('/puzzel/results', express.json(), async (req, res) => {
  const result = req.body;

  // One run posts several times as it goes. Key on the pair, not on an
  // insert: each call carries the whole state, so the newest simply wins.
  await db.results.upsert(
    { playerUid: result.playerUid, activityKey: result.activityKey },
    result
  );

  // Only act on the finish once.
  if (result.progress >= 100 || result.hasAlternateCompletion) {
    await markCourseStepComplete(result);
  }

  res.sendStatus(200);
});
Det udløses, mens du spiller — ikke kun til sidst

Et resultat sendes ud, hver gang indtastningen gemmes: efter en pause i skrivningen, når et kort placeres, når uret stopper, og igen når aktiviteten er færdig. Et langt krydsord er et par dusin kald, ikke ét — så skriv din handler som en upsert med nøgle på playerUid og activityKey i stedet for som en insert. Hvert kald indeholder hele tilstanden, så det nyeste altid overskriver det sidste, og et kald, der går tabt, bliver rettet op af det næste.

Sådan kender du en afslutning fra en gemning

progress er en procentdel: 100 betyder, at aktiviteten er fuldført. Nogle få typer kan slutte uden at nå dertil — en quiz besvaret helt igennem, et løst løsningsfelt — og de bærer i stedet hasAlternateCompletion. Behandl begge dele som færdig.

Det bliver sendt af spillerens browser

POST-kaldet kommer fra den fane, aktiviteten spilles i, ikke fra en Puzzel-server. De fleste endpoints, der er bygget til at modtage webhooks, accepterer det allerede. Hvis dit aldrig ser et kald, er det derfor: browseren spørger om lov først, så besvar OPTIONS-preflighten med en Access-Control-Allow-Origin-header, og det rigtige POST-kald følger efter.

Behandl payloaden som noget, der hævdes, ikke som et bevis

Der er ingen signatur på kaldet, og det kommer fra en browser, du ikke selv styrer, så alle, der kigger på siden, kan sende dig ét ligeså. Det er fint til at udfylde en fremskridtsbjælke eller et dashboard. For alt, du ikke ville lade en elev sætte selv — en karakter, der tæller, et certifikat, en betaling — så tjek det mod resultaterne i dit eget Puzzel-dashboard, eller lad LMS'ets karakterforbindelse overføre scoren i stedet.

Resultater til siden omkring den

Hvis du indlejrer aktiviteten, kan du få den samme JSON sendt til din egen side i stedet for til en server. Slå "Send resultater til den overordnede side" til, og lyt efter beskeden. Intet forlader browseren, så der er intet endpoint at bygge og ingen CORS at tænke på.

index.html
<iframe
  src="https://puzzel.org/en/quiz/embed?p=-Nq8sample_activity_key"
  width="100%"
  height="700"
  frameborder="0"
  allowfullscreen
></iframe>

<script>
  window.addEventListener('message', (event) => {
    if (event.origin !== 'https://puzzel.org') return;

    const result = event.data;
    if (!result || !result.activityKey) return;

    // Same shape the webhook posts, same advice: it arrives repeatedly.
    saveProgress(result);
  });
</script>
Tjek, hvor beskeden kom fra

Din listener hører hver eneste besked, der sendes til siden, også fra andre frames og browserudvidelser. Sammenlign event.origin med https://puzzel.org, før du stoler på indholdet.

Samme payload, samme timing

Dette er webhookens tvilling: de samme felter, sendt på de samme tidspunkter. Alt under "Hvad et resultat indeholder" gælder også her.

Afslutningssignal

Den mindste kanal, til når selve resultatet ikke er din sag: slå "Send et afslutningssignal" til, og din side får én besked, i det øjeblik spilleren er færdig.

index.html
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://puzzel.org') return;
  if (event.data?.completed !== true) return;

  // { completed: true, activityKey: '-Nq8sample_activity_key' }
  unlockNextLesson(event.data.activityKey);
});
Hvad der kommer
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
Resultatet er allerede gemt, når det ankommer

Signalet sendes bevidst, efter den afsluttende gemning er landet, så en side, der reagerer ved at læse resultatet tilbage, vil finde det der.

Kun inde i en iframe

Begge beskedkanaler sender til den side, der rammer aktiviteten ind. Åbnet i sin egen fane er der ingen at fortælle det til, så der bliver ikke sendt noget.

Hvad et resultat indeholder

Én form, uanset hvilken kanal der bærer den. Spillerens svar er nøglet efter aktivitetens egne item-id'er, så de samme nøgler dukker op i correctUids.

FeltTypeHvad det gør
activityKey
altid
string
stringDen aktivitet, resultatet hører til. Samme nøgle, som du ser i aktivitetens egen URL, efter ?p=.
playerUid
altid
string
stringHvem der spillede, som et anonymt id. Stabilt for denne spiller på denne enhed, så det er det, du nøgler resultater på — det er ikke en e-mailadresse og ikke en Puzzel-konto.
player
nogle gange
object
objectDe tilmeldingsfelter, aktiviteten beder om, sådan som du har konfigureret dem: name, email, class, student_id og så videre. Fraværende, indtil spilleren har tilmeldt sig, og helt fraværende på en aktivitet, der ikke beder om noget.
progress
altid
number
numberHvor langt spilleren er nået, som en procentdel. 100 betyder færdig.
timePassed
altid
number
numberTid brugt på aktiviteten, i millisekunder.
lastPlayedAt
altid
number
numberHvornår dette resultat blev gemt, som et Unix-timestamp i millisekunder.
createdAt
nogle gange
number
numberHvornår forsøget blev startet, som et Unix-timestamp i millisekunder.
playerInput
nogle gange
object
objectHvad spilleren faktisk indtastede, nøglet efter id'et på det item, det hører til. Formen indeni afhænger af aktivitetstypen — et ord, en liste over placerede kort, en valgt mulighed.
correctUids
nogle gange
object
objectHvilke af de items der er rigtige, nøglet på samme måde. Fraværende, så længe der ikke er svaret på noget endnu.
score
nogle gange
number
numberPoint scoret, på de typer, der giver point for en runde. Fraværende alle andre steder — også på en runde, der reelt scorede nul, så tjek, at nøglen findes, før du læser den.
performance
nogle gange
number
numberEn types eget mål for, hvor godt det gik, hvor den holder ét — for eksempel ord i minuttet i tastetræning.
attempts
nogle gange
number
numberHvilken runde det er: 1 første gang, én mere for hver gang, der startes forfra. Kun på typer, der kan afslutte en runde tidligt og tæller de gentagne forsøg.
knockedOut
nogle gange
boolean
booleanRunden sluttede på et forkert svar og er slut uden at være gennemført.
hasAlternateCompletion
nogle gange
boolean
booleanAktiviteten blev afsluttet på en måde, der ikke når 100 % — en quiz besvaret helt igennem, et løsningsfelt løst. Behandl det som en gennemførelse.
missedKeys
nogle gange
array
arrayTegn, spilleren blev ved med at ramme forkert, de mest fejlramte først. Kun i tastetræning.
contentVersion
nogle gange
number
numberHvilken version af aktivitetens indhold, dette blev spillet mod. Den ændrer sig, når ejeren redigerer spørgsmålene, så et gammelt resultat kan kendes fra et aktuelt.
En færdig quiz, som den ankommer
{
  "activityKey": "-Nq8sample_activity_key",
  "playerUid": "kK3r9TzSampleAnonymousUid",
  "player": {
    "name": "Ava Ortega",
    "email": "ava@school.example",
    "class": "5B"
  },
  "progress": 100,
  "timePassed": 243120,
  "lastPlayedAt": 1758297843120,
  "createdAt": 1758297600000,
  "playerInput": {
    "-Nq8item_one": "Lisbon",
    "-Nq8item_two": "1969"
  },
  "correctUids": {
    "-Nq8item_one": true,
    "-Nq8item_two": false
  },
  "score": 800,
  "contentVersion": 1757923200000
}
Fraværende, ikke tomt

Et felt, der ikke er relevant, udelades af JSON'en i stedet for at blive sendt som null eller nul. Sådan kan en type, der ikke giver point for en runde, kendes fra en runde, der scorede nul — så læs med en standardværdi, og gå aldrig ud fra, at en nøgle er der.

Nulstil aktiviteten fra din side

Én instruktion rejser den anden vej. Med "Modtag triggere fra den overordnede side" slået til kan den side, der indlejrer, rydde spillerens svar og sætte aktiviteten tilbage til starten — til din egen "prøv igen"-knap uden for frame'n.

reset
const frame = document.querySelector('iframe').contentWindow;

// Sent to the frame, from the page that embeds it — no other sender is
// accepted, and the activity has to have the trigger setting switched on.
frame.postMessage({ trigger: { type: 'reset' } }, 'https://puzzel.org');
Nulstil er den eneste trigger

Der er ingen besked til at indsende et resultat, åbne afslutningsbeskeden eller hoppe til et spørgsmål. En besked, der beder om andet end en nulstilling, bliver ignoreret.

Kun den indlejrende side bliver hørt

Triggeren accepteres kun fra den side, der rammer aktiviteten ind, og ingen andre steder fra — ikke en søsterframe, ikke et script på siden. Indstillingen er slået fra som standard, så slå den til for de aktiviteter, du styrer.

Medbring din egen spilleridentitet

Hvis din platform allerede ved, hvem der spiller, behøver aktiviteten ikke spørge igen. Der findes to håndtryk, begge til en aktivitet inde i din side, og begge slås til af os frem for i editoren — de ændrer, hvem et resultat hører til, så de sættes op sammen med dig frem for via et afkrydsningsfelt.

Send os spilleren

Aktiviteten melder sig selv med 'app-loaded' og venter. Din side sender spillerens oplysninger tilbage, og resultatet arkiveres under dem, uden at spilleren skal skrive noget eller se en tilmeldingsskærm.

Send os et token

Samme håndtryk, men din side sender den JWT, din identitetsudbyder har udstedt, i stedet for selve felterne. Vi verificerer den mod de issuers, der er sat op for din konto, før spilleren lukkes ind, så identiteten er bevist frem for hævdet — det er den, du skal bede om, når resultatet skal være troværdigt.

index.html
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://puzzel.org') return;

  // The activity says it is ready for a player.
  if (event.data === 'app-loaded') {
    frame.postMessage(
      {
        player: { name: 'Ava Ortega', email: 'ava@school.example', class: '5B' },
        // true starts a fresh attempt instead of resuming this player's last one
        should_reset: false
      },
      'https://puzzel.org'
    );
  }

  // Signed in; the board is coming.
  if (event.data?.success === true) hideYourOwnSpinner();
});
Bed os om at slå det til

Fortæl os, hvilken af de to du vil have, og hvor aktiviteterne skal indlejres, så sætter vi din konto op og gennemgår det sammen med dig.

Skriv til os om identitet

Opret aktiviteter fra dit eget system

Alt ovenfor handler om, at et resultat kommer ud. Den anden vej — at lave selve aktiviteterne ud fra indhold, du allerede har — er Puzzle API'et: ét POST-kald pr. aktivitetstype, og du får en nøgle og en URL til indlejring tilbage.

Læs API-referencen

Når en færdig forbindelse er det bedre svar

Hvis platformen i den anden ende er et rigtigt LMS, har du sandsynligvis ikke brug for noget af dette. Karakterer kan gå tilbage til dets karakterbog af sig selv, uden at du skal hoste noget.

Ikke nogen af dem?

Kursusplatforme, medlemssider, intranet og alt, du selv har bygget, er lige præcis det, kanalerne på denne side er til. En indlejring plus afslutningssignalet dækker det meste.

Hvad der ikke findes

Så du ikke går og leder efter det:

  • Intet endpoint til at læse resultater tilbage. API'et opretter aktiviteter; resultater forlader systemet gennem kanalerne på denne side eller gennem eksporterne i dit dashboard.
  • Ingen signatur på webhooken. Der er intet at verificere kaldet mod, og derfor bør et resultat ikke være det eneste, der står bag noget, der betyder noget.
  • Ingen kontobred webhook. URL'en er en indstilling på en aktivitet, så en aktivitet, du kopierer, tager den med, og en ny starter uden den.
  • Ingenting i et holdspil eller et live-rum. Både beskedkanalerne og webhooken er til solospil, og indstillingerne slår sig selv fra, når holdspil er slået til.
  • Ingen leveringskø. Intet bliver gemt og sendt igen — den næste gemning er forsøget igen, og rundens sidste kald er det, der tæller.

Opfører noget sig ikke, som det skal?

Send den anmodning, du prøvede, og den fejl, du fik tilbage, så får du et rigtigt svar — fra den person, der skrev endpointet.

Skriv til support