Sari la conținut
Integrări pentru dezvoltatori

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?

Din Puzzel
În Puzzel

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

Configurarea
  1. 1 Deschide activitatea în editor și mergi la meniul Dezvoltator.
  2. 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. 3 Joacă activitatea o dată, tu însuți. Primul POST ajunge imediat ce răspunzi la ceva.
Un receptor, de la un capăt la altul
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);
});
Se declanșează pe măsură ce joci, nu doar la final

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.

Cum deosebești o finalizare de o simplă salvare

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.

Este trimis de browserul jucătorului

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

Tratează payload-ul ca pe o afirmație, nu ca pe o dovadă

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.

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>
Verifică de unde a venit mesajul

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.

Același payload, aceeași cadență

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

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);
});
Ce sosește
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
Rezultatul este deja salvat când sosește

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.

Doar în interiorul unui iframe

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âmpTipCe face
activityKey
mereu
string
stringActivitatea 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
stringCine 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
objectCâ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
numberCât de departe a ajuns, ca procentaj. 100 înseamnă terminat.
timePassed
mereu
number
numberTimpul petrecut pe activitate, în milisecunde.
lastPlayedAt
mereu
number
numberCând a fost salvat acest rezultat, ca timestamp Unix în milisecunde.
createdAt
uneori
number
numberCând a început încercarea, ca timestamp Unix în milisecunde.
playerInput
uneori
object
objectCe 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
objectCare dintre acele elemente sunt corecte, indexate la fel. Absent cât timp nu s-a răspuns încă la nimic.
score
uneori
number
numberPuncte 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
numberMă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
numberCare 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
booleanRunda s-a încheiat pe un răspuns greșit și s-a terminat fără să fie completă.
hasAlternateCompletion
uneori
boolean
booleanActivitatea 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
arrayCaracterele la care jucătorul a greșit constant, cele mai ratate primele. Doar la exercițiul de tastare.
contentVersion
uneori
number
numberCe 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.
Un quiz terminat, așa cum sosește
{
  "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
}
Absent, nu gol

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.

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');
Resetarea este singura comandă

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.

Doar pagina de încorporare este auzită

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.

Trimite-ne jucătorul

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.

Trimite-ne un token

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.

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();
});
Cere-ne să-l activăm

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 identitate

Crearea 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 API

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

Niciunul dintre acestea?

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