Hoppa till innehållet
Integrationer för utvecklare

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?

Ut från Puzzel
In till Puzzel

Resultatwebhook

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.

Så ställer du in det
  1. 1 Öppna aktiviteten i redigeraren och gå till menyn Utvecklare.
  2. 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. 3 Spela aktiviteten en gång själv. Den första POST:en kommer in så fort du svarar på något.
En mottagare, från början till slut
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);
});
Den utlöses medan du spelar, inte bara i slutet

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.

Att skilja ett avslut från ett sparande

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.

Den skickas av spelarens webbläsare

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.

Behandla nyttolasten som ett påstående, inte ett bevis

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

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>
Kontrollera var meddelandet kom ifrån

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.

Samma nyttolast, samma tidpunkter

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.

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);
});
Vad som kommer in
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
Resultatet är redan sparat när det kommer in

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.

Bara inuti en iframe

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ältTypVad det gör
activityKey
alltid
string
stringAktiviteten resultatet hör till. Samma nyckel som du ser i aktivitetens egen URL, efter ?p=.
playerUid
alltid
string
stringVem 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
objectDe 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
numberHur långt spelaren kommit, som en procentsats. 100 betyder klar.
timePassed
alltid
number
numberTid på aktiviteten, i millisekunder.
lastPlayedAt
alltid
number
numberNär det här resultatet sparades, som en Unix-tidsstämpel i millisekunder.
createdAt
ibland
number
numberNär försöket startades, som en Unix-tidsstämpel i millisekunder.
playerInput
ibland
object
objectVad 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
objectVilka av de objekten som är rätt, nyckelsatta på samma sätt. Saknas så länge inget har besvarats än.
score
ibland
number
numberPoä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
numberEn 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
numberVilken 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
booleanOmgången avslutades på ett fel svar och är över utan att vara klar.
hasAlternateCompletion
ibland
boolean
booleanAktiviteten 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
arrayTecken spelaren fortsatte att skriva fel, de mest missade först. Bara tangentbordsträning.
contentVersion
ibland
number
numberVilken 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.
Ett avslutat quiz, som det kommer in
{
  "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
}
Saknas, inte tomt

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.

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');
Nollställning är det enda kommandot

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.

Bara den inbäddande sidan hörs

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.

Skicka spelaren till oss

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.

Skicka en token till oss

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

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();
});
Be oss slå på det

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 identitet

Skapa 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-referensen

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

Inget av de här?

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