Koppla samman Puzzel med din egen plattform
Puzzel kan lämna en spelares resultat direkt till ett annat system — poängen, hur långt de kom, vad de svarade — utan att det systemet behöver vara ett fullständigt LMS. Den här sidan visar alla kanaler som finns: vad som skickas, när det skickas, och det minsta du behöver bygga för att ta emot det.
- Resultat skickas via
- En webhook, eller ett meddelande till sidan runt aktiviteten
- Din sida kan
- Logga in spelaren och nollställa aktiviteten
- Slås på
- Per aktivitet, under Utvecklare i redigeraren
- Ingår i
- Ett betalpaket — de här inställningarna är avstängda på ett gratiskonto
Vilken kanal behöver du?
Tre saker kan lämna en aktivitet och en kan komma in. Vilken som passar avgörs av en enda fråga: sitter spelaren inne i din sida, eller någon helt annanstans?
Varje sparat resultat POSTas som JSON till en URL du äger.
- Använd den när
- Spelaren kan vara var som helst — en delad länk, en QR-kod, någon annans webbplats — och du vill ha resultatet i din egen databas.
- Du behöver
- En HTTPS-slutpunkt som tar emot en cross-origin-POST.
save_puzzle_results_via_webhookSamma JSON, skickad till sidan som bäddar in aktiviteten istället för till en server.
- Använd den när
- Du bäddar in aktiviteten på din egen kurssida och sidan själv kan göra något med resultatet.
- Du behöver
- En iframe på din sida och en meddelandelyssnare. Ingen server, ingen CORS.
save_results_iframe_postmessageEtt meddelande när spelaren är klar, som inte innehåller något annat än det faktumet.
- Använd den när
- Allt du vill veta är om de är klara — för att bocka av lektionen, låsa upp nästa, eller visa din egen skärm.
- Du behöver
- En iframe på din sida och en meddelandelyssnare.
send_completion_signal_when_embeddedResultatwebhook
Slå på "Skicka resultat till en webhook" i redigeraren och ange en URL. Från och med då POSTar spelarens webbläsare hela resultatet som JSON till den URL:en varje gång deras framsteg sparas.
- 1 Öppna aktiviteten i redigeraren och gå till menyn Utvecklare.
- 2 Slå på "Skicka resultat till en webhook" och klistra in din slutpunkt i fältet under. Det måste vara en fullständig URL — en bar domän avvisas — och den måste vara https, eftersom webbläsaren blockerar ett vanligt http-anrop från en sida som levereras via https.
- 3 Spela aktiviteten en gång själv. Den första POST:en kommer in så fort du svarar på något.
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);
});Ett resultat skickas varje gång posten sparas: efter en paus i skrivandet, när ett kort placeras, när klockan stannar, och en gång till när aktiviteten är klar. Ett långt korsord är ett tjugotal anrop, inte ett — så skriv din hanterare som en upsert nyckelsatt på playerUid och activityKey istället för som en insert. Varje anrop bär det fullständiga tillståndet, så det senaste ersätter alltid det föregående, och ett anrop som uteblir kompenseras av nästa.
progress är en procentsats: 100 betyder att aktiviteten är klar. Några typer kan avslutas utan att nå dit — ett quiz som besvarats hela vägen igenom, ett löst lösningsfält — och de bär istället hasAlternateCompletion. Behandla båda som avslutade.
POST:en kommer från fliken där aktiviteten spelas, inte från en Puzzel-server. De flesta slutpunkter som byggts för att ta emot webhooks accepterar den redan. Om din aldrig ser en förfrågan är det här förklaringen: webbläsaren frågar om lov först, så svara på OPTIONS-preflighten med en Access-Control-Allow-Origin-header, sedan följer den riktiga POST:en.
Det finns ingen signatur på förfrågan, och den kommer från en webbläsare du inte kontrollerar, så vem som helst som tittar på sidan kan skicka en till dig också. Det duger för att fylla i en förloppsindikator eller en instrumentpanel. För allt du inte skulle låta en elev sätta själv — ett betyg som räknas, ett certifikat, en betalning — kontrollera det mot resultaten i din egen Puzzel-instrumentpanel, eller låt LMS-betygskopplingarna föra över poängen istället.
Resultat till sidan runt om
Om du bäddar in aktiviteten kan du få samma JSON skickad till din egen sida istället för till en server. Slå på "Skicka resultat till den överordnade sidan" och lyssna efter meddelandet. Inget lämnar webbläsaren, så det finns ingen slutpunkt att bygga och ingen CORS att tänka på.
<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>Din lyssnare hör varje meddelande som skickas till sidan, inklusive från andra ramar och webbläsartillägg. Jämför event.origin med https://puzzel.org innan du litar på innehållet.
Det här är webhookens tvilling: samma fält, skickade vid samma tillfällen. Allt under "Vad ett resultat innehåller" gäller även här.
Klarsignal
Den minsta kanalen, för när själva resultatet inte är din sak: slå på "Skicka en klarsignal" så får din sida ett meddelande i samma stund som spelaren blir klar.
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);
});{
"completed": true,
"activityKey": "-Nq8sample_activity_key"
}Signalen skickas avsiktligt efter att det avslutande sparandet har landat, så en sida som reagerar genom att läsa tillbaka resultatet hittar det där.
Båda meddelandekanalerna skickar till sidan som ramar in aktiviteten. Öppnad i en egen flik finns det ingen att meddela, så inget skickas.
Vad ett resultat innehåller
En och samma form, oavsett vilken kanal som bär den. Spelarens svar är nyckelsatta med aktivitetens egna objekt-id:n, så samma nycklar dyker upp i correctUids.
| Fält | Typ | Vad det gör |
|---|---|---|
activityKey alltid string | string | Aktiviteten resultatet hör till. Samma nyckel som du ser i aktivitetens egen URL, efter ?p=. |
playerUid alltid string | string | Vem som spelade, som ett anonymt id. Stabilt för den här spelaren på den här enheten, så det är det du nyckelsätter resultat på — det är varken en e-postadress eller ett Puzzel-konto. |
player ibland object | object | De registreringsfält aktiviteten frågar efter, så som du konfigurerat dem: name, email, class, student_id och så vidare. Saknas tills spelaren har registrerat sig, och saknas helt på en aktivitet som inte frågar efter något. |
progress alltid number | number | Hur långt spelaren kommit, som en procentsats. 100 betyder klar. |
timePassed alltid number | number | Tid på aktiviteten, i millisekunder. |
lastPlayedAt alltid number | number | När det här resultatet sparades, som en Unix-tidsstämpel i millisekunder. |
createdAt ibland number | number | När försöket startades, som en Unix-tidsstämpel i millisekunder. |
playerInput ibland object | object | Vad spelaren faktiskt skrev in, nyckelsatt med id:t för det objekt det hör till. Formen inuti beror på aktivitetstypen — ett ord, en lista med placerade kort, ett valt alternativ. |
correctUids ibland object | object | Vilka av de objekten som är rätt, nyckelsatta på samma sätt. Saknas så länge inget har besvarats än. |
score ibland number | number | Poäng som gjorts, på de typer som poängsätter en omgång. Saknas överallt annars — även på en omgång som verkligen fick noll poäng, så kontrollera att nyckeln finns innan du läser den. |
performance ibland number | number | En typs eget mått på hur det gick, där ett sådant finns — ord per minut i tangentbordsträning, till exempel. |
attempts ibland number | number | Vilken omgång det här är: 1 första gången, en till för varje omstart. Bara på typer som avslutar en omgång i förtid och räknar omtagningarna. |
knockedOut ibland boolean | boolean | Omgången avslutades på ett fel svar och är över utan att vara klar. |
hasAlternateCompletion ibland boolean | boolean | Aktiviteten avslutades på ett sätt som inte når 100 % — ett quiz som besvarats hela vägen igenom, ett löst lösningsfält. Behandla det som avslutat. |
missedKeys ibland array | array | Tecken spelaren fortsatte att skriva fel, de mest missade först. Bara tangentbordsträning. |
contentVersion ibland number | number | Vilken version av aktivitetens innehåll det här spelades mot. Den ändras när ägaren redigerar frågorna, så ett gammalt resultat kan skiljas från ett aktuellt. |
{
"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
}Ett fält som inte är tillämpligt utelämnas ur JSON:en istället för att skickas som null eller noll. Det är så en typ som inte poängsätter en omgång skiljs från en omgång som fick noll poäng — så läs med ett standardvärde och anta aldrig att en nyckel finns.
Nollställa aktiviteten från din sida
En instruktion går åt andra hållet. Med "Ta emot kommandon från den överordnade sidan" påslagen kan sidan som gör inbäddningen rensa spelarens svar och sätta tillbaka aktiviteten till starten — för en egen knapp för "försök igen" utanför ramen.
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');Det finns inget meddelande för att skicka in ett resultat, öppna slutmeddelandet eller hoppa till en fråga. Ett meddelande som ber om något annat än en nollställning ignoreras.
Kommandot accepteras bara från sidan som ramar in aktiviteten och ingen annanstans ifrån — inte en syskonram, inte ett skript på sidan. Inställningen är avstängd som standard, så slå på den för de aktiviteter du driver.
Ta med din egen spelaridentitet
Om din plattform redan vet vem som spelar behöver aktiviteten inte fråga igen. Det finns två handskakningar, båda för en aktivitet inne i din sida, och båda slås på av oss istället för i redigeraren — de ändrar vem ett resultat tillhör, så de ställs in tillsammans med dig istället för via en kryssruta.
Aktiviteten meddelar sig själv med 'app-loaded' och väntar. Din sida skickar tillbaka spelarens uppgifter, och resultatet arkiveras under dem utan att spelaren skriver något eller ser en registreringsskärm.
Samma handskakning, men din sida skickar JWT:n som din identitetsleverantör utfärdat istället för själva fälten. Vi verifierar den mot de utfärdare som är inställda för ditt konto innan spelaren släpps in, så identiteten bevisas istället för att bara påstås — det här är den att be om när resultatet måste gå att lita på.
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();
});Berätta för oss vilken av de två du vill ha och var aktiviteterna kommer att bäddas in, så ställer vi in ditt konto och går igenom det tillsammans med dig.
Skriv till oss om identitetSkapa aktiviteter från ditt system
Allt ovanför handlar om att ett resultat kommer ut. Att göra tvärtom — skapa själva aktiviteterna från innehåll du redan har — är Puzzle API:et: en POST per aktivitetstyp, och du får tillbaka en nyckel och en URL att bädda in.
Läs API-referensenNär en färdig koppling är det bättre svaret
Om plattformen på andra sidan är ett riktigt LMS behöver du förmodligen inget av det här. Betyg kan gå tillbaka till dess betygsbok av sig själva, utan att du behöver driva något själv.
Startas inifrån LMS:et, med poängen skriven tillbaka till dess betygsbok.
Lägg upp en aktivitet som en uppgift och få betygen tillbaka automatiskt.
Kursplattformar, medlemssidor, intranät och allt du byggt själv är precis vad kanalerna på den här sidan är till för. En inbäddning plus klarsignalen täcker det mesta.
Vad som inte finns
Så att du inte letar efter det:
- Ingen slutpunkt för att läsa tillbaka resultat. API:et skapar aktiviteter; resultat lämnar genom kanalerna på den här sidan, eller genom exporterna i din instrumentpanel.
- Ingen signatur på webhooken. Det finns inget att verifiera förfrågan mot, vilket är varför ett resultat inte bör vara det enda som står bakom något som är viktigt.
- Ingen kontoomfattande webhook. URL:en är en inställning på en aktivitet, så en aktivitet du kopierar för med sig den, och en ny börjar utan den.
- Inget i ett lagspel eller ett live-rum. Både meddelandekanalerna och webhooken är till för spel på egen hand, och inställningarna stänger av sig själva när lagspel är påslaget.
- Ingen leveranskö. Inget lagras och skickas om — nästa sparning är omförsöket, och omgångens sista anrop är det som räknas.
Något som inte funkar som det ska?
Skicka anropet du testade och felet du fick tillbaka, så får du ett riktigt svar från personen som skrev endpointen.
E-posta support