Conectează Puzzel la propria ta platformă
Puzzel poate trimite rezultatul unui jucător direct către un alt sistem — scorul lui, cât a parcurs, ce a răspuns — fără ca acel sistem să fie un LMS complet. Această pagină este fiecare canal care există: ce iese, când iese și cel mai mic lucru pe care trebuie să-l construiești ca să îl preiei.
- Rezultatele ies prin
- Un webhook, sau un mesaj către pagina din jurul activității
- Pagina ta poate
- Să conecteze jucătorul și să reseteze activitatea
- Se activează
- Per activitate, la Dezvoltator, în editor
- Inclus în
- Un plan cu plată — aceste setări sunt dezactivate pe un cont gratuit
De care canal ai nevoie?
Trei lucruri pot ieși dintr-o activitate și unul poate intra. Care ți se potrivește depinde de o singură întrebare: jucătorul stă în pagina ta sau cu totul altundeva?
Fiecare rezultat salvat este trimis (POST) ca JSON către un URL deținut de tine.
- Folosește-l când
- Jucătorul poate fi oriunde — un link partajat, un cod QR, site-ul altcuiva — și vrei rezultatul în propria bază de date.
- Ai nevoie de
- Un endpoint HTTPS care acceptă un POST cross-origin.
save_puzzle_results_via_webhookAcelași JSON, trimis către pagina care încorporează activitatea în loc de către un server.
- Folosește-l când
- Încorporezi activitatea în propria pagină de curs, iar pagina în sine poate face ceva cu rezultatul.
- Ai nevoie de
- Un iframe pe pagina ta și un listener de mesaje. Fără server, fără CORS.
save_results_iframe_postmessageUn singur mesaj când jucătorul termină, care nu poartă altceva decât acest fapt.
- Folosește-l când
- Tot ce vrei să știi e dacă a terminat — ca să bifezi lecția, să deblochezi următoarea sau să afișezi propriul ecran.
- Ai nevoie de
- Un iframe pe pagina ta și un listener de mesaje.
send_completion_signal_when_embeddedWebhook pentru rezultate
Activează „Trimite rezultatele către un webhook” în editor și dă-i un URL. Din acel moment, de fiecare dată când progresul jucătorului este salvat, browserul lui trimite (POST) întregul rezultat către acel URL, ca JSON.
- 1 Deschide activitatea în editor și mergi la meniul Dezvoltator.
- 2 Activează „Trimite rezultatele către un webhook” și lipește endpoint-ul tău în câmpul de mai jos. Trebuie să fie un URL complet — un domeniu simplu este refuzat — și trebuie să fie https, pentru că browserul blochează un apel http simplu făcut dintr-o pagină servită prin https.
- 3 Joacă activitatea o dată, tu însuți. Primul POST ajunge imediat ce răspunzi la ceva.
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);
});Un rezultat iese de fiecare dată când intrarea este salvată: după o pauză în tastare, când un cartonaș este plasat, când cronometrul se oprește și încă o dată când activitatea este terminată. Un careu lung de cuvinte încrucișate înseamnă câteva zeci de apeluri, nu unul singur — așa că scrie-ți handlerul ca un upsert cu playerUid și activityKey drept cheie, nu ca o inserare. Fiecare apel poartă starea completă, așa că cel mai nou o înlocuiește mereu pe ultima, iar un apel pierdut este compensat de următorul.
progress este un procentaj: 100 înseamnă că activitatea e completă. Câteva tipuri se pot încheia fără să ajungă acolo — un quiz răspuns integral, un câmp de soluție rezolvat — și acestea poartă în schimb hasAlternateCompletion. Tratează oricare dintre cele două ca finalizat.
POST-ul vine din tab-ul în care se joacă activitatea, nu de la un server Puzzel. Majoritatea endpoint-urilor construite să primească webhook-uri îl acceptă deja. Dacă al tău nu vede niciodată o cerere, iată de ce: browserul cere mai întâi permisiune, așa că răspunde cererii preflight OPTIONS cu un header Access-Control-Allow-Origin, iar POST-ul real urmează.
Cererea nu are nicio semnătură și vine dintr-un browser pe care nu îl controlezi, așa că oricine se uită la pagină îți poate trimite și el unul. E în regulă pentru a completa o bară de progres sau un tablou de bord. Pentru orice n-ai lăsa un elev să-și seteze singur — o notă care contează, un certificat, o plată — verifică-l în raport cu rezultatele din propriul tablou de bord Puzzel, sau lasă conectorii de note ai LMS-ului să poarte scorul în locul lui.
Rezultate către pagina din jur
Dacă încorporezi activitatea, poți primi același JSON trimis către propria pagină în loc de către un server. Activează „Trimite rezultatele către pagina părinte” și ascultă mesajul. Nimic nu iese din browser, așa că nu ai niciun endpoint de construit și niciun CORS la care să te gândești.
<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>Listener-ul tău aude fiecare mesaj trimis către pagină, inclusiv din alte cadre sau extensii de browser. Compară event.origin cu https://puzzel.org înainte să ai încredere în ce conține.
Acesta este geamănul webhook-ului: aceleași câmpuri, trimise în aceleași momente. Tot ce se află sub „Ce conține un rezultat” se aplică și aici.
Semnal de finalizare
Cel mai mic canal, pentru când rezultatul în sine nu te privește: activează „Trimite un semnal de finalizare”, iar pagina ta primește un singur mesaj în clipa în care jucătorul termină.
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"
}Semnalul este trimis intenționat după ce salvarea de finalizare s-a produs, așa că o pagină care reacționează citind rezultatul înapoi îl va găsi acolo.
Ambele canale de mesaje trimit către pagina care încadrează activitatea. Deschisă în propriul tab, nu are cui să-i spună, așa că nu se trimite nimic.
Ce conține un rezultat
O singură formă, indiferent de canalul care o poartă. Răspunsurile jucătorului sunt indexate după id-urile proprii ale elementelor activității, așa că aceleași chei apar și în correctUids.
| Câmp | Tip | Ce face |
|---|---|---|
activityKey mereu string | string | Activitatea căreia îi aparține rezultatul. Aceeași cheie pe care o vezi în URL-ul propriu al activității, după ?p=. |
playerUid mereu string | string | Cine a jucat, ca id anonim. Stabil pentru acest jucător pe acest dispozitiv, deci e ceea ce folosești ca cheie pentru rezultate — nu este o adresă de e-mail și nu este un cont Puzzel. |
player uneori object | object | Câmpurile de înregistrare pe care le cere activitatea, așa cum le-ai configurat: nume, e-mail, clasă, student_id și așa mai departe. Absent până când jucătorul s-a înregistrat, și absent complet la o activitate care nu cere nimic. |
progress mereu number | number | Cât de departe a ajuns, ca procentaj. 100 înseamnă terminat. |
timePassed mereu number | number | Timpul petrecut pe activitate, în milisecunde. |
lastPlayedAt mereu number | number | Când a fost salvat acest rezultat, ca timestamp Unix în milisecunde. |
createdAt uneori number | number | Când a început încercarea, ca timestamp Unix în milisecunde. |
playerInput uneori object | object | Ce a introdus efectiv jucătorul, indexat după id-ul elementului căruia îi aparține. Forma din interior depinde de tipul activității — un cuvânt, o listă de cartonașe plasate, o opțiune aleasă. |
correctUids uneori object | object | Care dintre acele elemente sunt corecte, indexate la fel. Absent cât timp nu s-a răspuns încă la nimic. |
score uneori number | number | Puncte obținute, la tipurile care punctează o rundă. Absent peste tot în rest — inclusiv la o rundă care a obținut cu adevărat zero, așa că verifică dacă cheia există înainte să o citești. |
performance uneori number | number | Măsura proprie a unui tip pentru cât de bine a mers, acolo unde ține una — cuvinte pe minut la exercițiul de tastare, de exemplu. |
attempts uneori number | number | Care rundă este aceasta: 1 prima dată, câte una în plus la fiecare reluare. Doar la tipurile care încheie o rundă devreme și numără reluările. |
knockedOut uneori boolean | boolean | Runda s-a încheiat pe un răspuns greșit și s-a terminat fără să fie completă. |
hasAlternateCompletion uneori boolean | boolean | Activitatea a fost terminată într-un fel care nu ajunge la 100% — un quiz răspuns integral, un câmp de soluție rezolvat. Tratează-l ca pe o finalizare. |
missedKeys uneori array | array | Caracterele la care jucătorul a greșit constant, cele mai ratate primele. Doar la exercițiul de tastare. |
contentVersion uneori number | number | Ce versiune a conținutului activității a fost jucată. Se schimbă când proprietarul editează întrebările, așa că un rezultat vechi poate fi deosebit de unul actual. |
{
"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
}Un câmp care nu se aplică este omis din JSON, nu trimis ca null sau zero. Așa se deosebește un tip care nu punctează o rundă de o rundă care a punctat zero — așa că citește cu o valoare implicită și nu presupune niciodată că o cheie e prezentă.
Resetarea activității din pagina ta
O singură instrucțiune circulă în celălalt sens. Cu „Acceptă comenzi de la pagina părinte” activat, pagina care face încorporarea poate șterge răspunsurile jucătorului și poate readuce activitatea la început — pentru propriul tău buton „încearcă din nou”, din afara cadrului.
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');Nu există niciun mesaj pentru trimiterea unui rezultat, deschiderea mesajului de final sau sărirea la o întrebare. Un mesaj care cere altceva decât o resetare este ignorat.
Comanda este acceptată de la pagina care încadrează activitatea și de nicăieri altundeva — nu de la un cadru vecin, nu de la un script de pe pagină. Setarea este dezactivată implicit, așa că activeaz-o pentru activitățile pe care le conduci tu.
Aducerea propriei identități de jucător
Dacă platforma ta știe deja cine joacă, activitatea nu mai trebuie să întrebe din nou. Există două tipuri de handshake, ambele pentru o activitate din interiorul paginii tale, și ambele activate de noi, nu din editor — schimbă cui îi aparține un rezultat, așa că se configurează împreună cu tine, nu dintr-o casetă de bifat.
Activitatea se anunță cu 'app-loaded' și așteaptă. Pagina ta trimite înapoi datele jucătorului, iar rezultatul este înregistrat pe numele lui fără ca jucătorul să tasteze ceva sau să vadă un ecran de înregistrare.
Același handshake, dar pagina ta trimite JWT-ul emis de furnizorul tău de identitate, în loc de câmpurile în sine. Îl verificăm în raport cu emitenții configurați pentru contul tău înainte ca jucătorul să fie lăsat să intre, așa că identitatea este dovedită, nu doar afirmată — acesta e cel de cerut atunci când rezultatul trebuie să fie de încredere.
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();
});Spune-ne pe care dintre cele două îl vrei și unde vor fi încorporate activitățile, iar noi îți configurăm contul și trecem prin proces împreună cu tine.
Scrie-ne despre identitateCrearea activităților din sistemul tău
Tot ce e mai sus se referă la un rezultat care iese. În celălalt sens — crearea activităților în sine din conținut pe care îl ai deja — este Puzzle API: un POST pentru fiecare tip de activitate, iar tu primești înapoi o cheie și un URL de încorporat.
Citește referința APICând un conector gata făcut e răspunsul mai bun
Dacă platforma de cealaltă parte este un LMS adevărat, probabil nu ai nevoie de nimic din toate acestea. Notele pot ajunge singure în carnetul lui de note, fără nimic de găzduit din partea ta.
Lansată din interiorul LMS-ului, cu scorul scris înapoi în carnetul lui de note.
Postează o activitate ca temă și notele revin automat.
Platformele de curs, site-urile pe bază de abonament, intraneturile și orice ai construit tu însuți sunt exact pentru ce există canalele de pe această pagină. O încorporare plus semnalul de finalizare acoperă majoritatea cazurilor.
Ce nu există
Ca să nu îl cauți degeaba:
- Niciun endpoint pentru citirea rezultatelor înapoi. API-ul creează activități; rezultatele ies prin canalele de pe această pagină, sau prin exporturile din tabloul tău de bord.
- Nicio semnătură pe webhook. Nu există nimic în raport cu care să verifici cererea, motiv pentru care un rezultat nu ar trebui să fie singurul lucru din spatele a ceva ce contează.
- Niciun webhook la nivel de cont. URL-ul este o setare pe o activitate, așa că o activitate pe care o copiezi îl duce cu ea, iar una nouă pornește fără el.
- Nimic într-un joc pe echipe sau într-o cameră live. Ambele canale de mesaje și webhook-ul sunt pentru joc solo, iar setările se dezactivează singure când modul pe echipe este activ.
- Nicio coadă de livrare. Nimic nu este stocat și retrimis — următoarea salvare este reîncercarea, iar ultimul apel al rundei este cel care contează.
Ceva nu funcționează cum trebuie?
Trimite cererea pe care ai încercat-o și eroarea pe care ai primit-o înapoi și vei primi un răspuns real, de la persoana care a scris endpointul.
Trimite un e-mail suportului