Zum Inhalt springen
Entwickler-Integrationen

Puzzel in deine eigene Plattform einbinden

Puzzel kann das Ergebnis eines Spielers direkt an ein anderes System übergeben — die Punktzahl, wie weit er gekommen ist, was er geantwortet hat —, ohne dass dieses System ein vollständiges LMS sein muss. Diese Seite zeigt jeden Kanal, den es gibt: was rausgeht, wann es rausgeht, und das Kleinste, was du bauen musst, um es aufzufangen.

Ergebnisse gehen raus über
Einen Webhook oder eine Nachricht an die Seite rund um die Aktivität
Deine Seite kann
Den Spieler anmelden und die Aktivität zurücksetzen
Aktiviert
Pro Aktivität, unter Entwickler im Editor
Enthalten bei
Einem bezahlten Tarif — bei einem kostenlosen Konto sind diese Einstellungen deaktiviert

Welchen Kanal brauchst du?

Drei Dinge können eine Aktivität verlassen, eines kann hineinkommen. Was passt, hängt von einer einzigen Frage ab: Sitzt der Spieler auf deiner eigenen Seite oder ganz woanders?

Aus Puzzel heraus
In Puzzel hinein

Ergebnis-Webhook

Aktivier "Ergebnisse an einen Webhook senden" im Editor und gib eine URL an. Von da an sendet der Browser des Spielers bei jedem Speichern des Fortschritts das komplette Ergebnis als JSON per POST an diese URL.

Einrichtung
  1. 1 Öffne die Aktivität im Editor und geh zum Menü Entwickler.
  2. 2 Aktivier "Ergebnisse an einen Webhook senden" und füg deinen Endpunkt in das Feld darunter ein. Es muss eine vollständige URL sein — eine reine Domain wird abgelehnt — und sie muss https sein, weil der Browser einen reinen HTTP-Aufruf von einer über https ausgelieferten Seite aus blockiert.
  3. 3 Spiel die Aktivität einmal selbst durch. Der erste POST kommt an, sobald du etwas beantwortest.
Ein Empfänger, von Anfang bis Ende
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);
});
Es feuert während des Spielens, nicht erst am Ende

Ein Ergebnis geht jedes Mal raus, wenn der Eintrag gespeichert wird: nach einer Tipppause, wenn eine Karte platziert wird, wenn die Uhr stoppt, und noch einmal, wenn die Aktivität abgeschlossen ist. Bei einem langen Kreuzworträtsel sind das ein paar Dutzend Aufrufe, nicht einer — schreib deinen Handler also als Upsert mit playerUid und activityKey als Schlüssel, nicht als Insert. Jeder Aufruf enthält den vollständigen Zustand, sodass der neueste immer den letzten ersetzt und ein verlorener Aufruf vom nächsten wieder ausgeglichen wird.

Einen Abschluss von einem normalen Speichern unterscheiden

progress ist ein Prozentwert: 100 bedeutet, die Aktivität ist abgeschlossen. Ein paar Typen können enden, ohne das zu erreichen — ein bis zum Ende beantwortetes Quiz, ein gelöstes Lösungsfeld — und tragen stattdessen hasAlternateCompletion. Behandle beides als abgeschlossen.

Es wird vom Browser des Spielers gesendet

Der POST kommt aus dem Tab, in dem die Aktivität gespielt wird, nicht von einem Puzzel-Server. Die meisten Endpunkte, die für den Empfang von Webhooks gebaut wurden, akzeptieren das bereits. Wenn deiner nie eine Anfrage sieht, liegt es daran: Der Browser fragt zuerst um Erlaubnis, also beantworte den OPTIONS-Preflight mit einem Access-Control-Allow-Origin-Header, dann folgt der eigentliche POST.

Behandle die Nutzdaten als Behauptung, nicht als Beweis

Die Anfrage trägt keine Signatur und kommt aus einem Browser, den du nicht kontrollierst — jeder, der sich die Seite ansieht, kann dir also auch eine schicken. Das ist völlig in Ordnung, um einen Fortschrittsbalken oder ein Dashboard zu füllen. Für alles, was du einen Schüler nicht selbst festlegen lassen würdest — eine zählende Note, ein Zertifikat, eine Zahlung — gleich es mit den Ergebnissen in deinem eigenen Puzzel-Dashboard ab, oder lass stattdessen die LMS-Notenanbindung die Punktzahl übertragen.

Ergebnisse an die umgebende Seite

Wenn du die Aktivität einbettest, kannst du dasselbe JSON an deine eigene Seite senden lassen statt an einen Server. Aktivier "Ergebnisse an die übergeordnete Seite senden" und lausche auf die Nachricht. Nichts verlässt den Browser, also musst du keinen Endpunkt bauen und dir keine Gedanken über CORS machen.

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>
Prüfe, woher die Nachricht kommt

Dein Listener hört jede Nachricht, die an die Seite gesendet wird — auch von anderen Frames und Browser-Erweiterungen. Vergleich event.origin mit https://puzzel.org, bevor du dem Inhalt vertraust.

Dieselben Nutzdaten, derselbe Zeitpunkt

Das ist das Gegenstück zum Webhook: dieselben Felder, gesendet zu denselben Zeitpunkten. Alles unter "Was ein Ergebnis enthält" gilt auch hier.

Abschlusssignal

Der kleinste Kanal, für den Fall, dass dich das Ergebnis selbst nichts angeht: Aktivier "Abschlusssignal senden" und deine Seite bekommt eine Nachricht in dem Moment, in dem der Spieler fertig ist.

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);
});
Was ankommt
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
Das Ergebnis ist bereits gespeichert, wenn es ankommt

Das Signal wird bewusst erst gesendet, nachdem der abschließende Speichervorgang abgeschlossen ist — eine Seite, die daraufhin das Ergebnis abruft, findet es dort bereits vor.

Nur innerhalb eines iframe

Beide Nachrichtenkanäle senden an die Seite, die die Aktivität einbettet. In einem eigenen Tab geöffnet gibt es niemanden, dem man es mitteilen könnte — also wird nichts gesendet.

Was ein Ergebnis enthält

Eine Form, egal welcher Kanal sie trägt. Die Antworten des Spielers sind nach den eigenen Element-IDs der Aktivität geschlüsselt, sodass dieselben Schlüssel auch in correctUids auftauchen.

FeldTypWas es macht
activityKey
immer
string
stringDie Aktivität, zu der das Ergebnis gehört. Derselbe Schlüssel, den du in der URL der Aktivität selbst siehst, nach ?p=.
playerUid
immer
string
stringWer gespielt hat, als anonyme ID. Für diesen Spieler auf diesem Gerät stabil, also der Wert, über den du Ergebnisse zuordnest — keine E-Mail-Adresse und kein Puzzel-Konto.
player
manchmal
object
objectDie Registrierungsfelder, nach denen die Aktivität fragt, so wie du sie konfiguriert hast: name, email, class, student_id und so weiter. Fehlt, solange sich der Spieler nicht registriert hat, und fehlt komplett bei einer Aktivität, die nichts abfragt.
progress
immer
number
numberWie weit fortgeschritten, als Prozentwert. 100 bedeutet fertig.
timePassed
immer
number
numberZeit auf der Aktivität, in Millisekunden.
lastPlayedAt
immer
number
numberWann dieses Ergebnis gespeichert wurde, als Unix-Zeitstempel in Millisekunden.
createdAt
manchmal
number
numberWann der Versuch begonnen wurde, als Unix-Zeitstempel in Millisekunden.
playerInput
manchmal
object
objectWas der Spieler tatsächlich eingegeben hat, geschlüsselt nach der ID des zugehörigen Elements. Die Form darin hängt vom Aktivitätstyp ab — ein Wort, eine Liste platzierter Karten, eine gewählte Option.
correctUids
manchmal
object
objectWelche dieser Elemente richtig sind, auf dieselbe Weise geschlüsselt. Fehlt, solange noch nichts beantwortet wurde.
score
manchmal
number
numberErzielte Punkte, bei den Typen, die einen Durchlauf bewerten. Fehlt überall sonst — auch bei einem Durchlauf, der tatsächlich null Punkte erzielt hat, also prüf, ob der Schlüssel existiert, bevor du ihn ausliest.
performance
manchmal
number
numberDas eigene Maß eines Typs dafür, wie gut es lief, sofern er eines führt — zum Beispiel Wörter pro Minute beim Tipptraining.
attempts
manchmal
number
numberDer wievielte Durchlauf das ist: 1 beim ersten Mal, jeweils eins mehr bei jedem Neustart. Nur bei Typen, die einen Durchlauf vorzeitig beenden und die Versuche zählen.
knockedOut
manchmal
boolean
booleanDer Durchlauf endete bei einer falschen Antwort und ist vorbei, ohne abgeschlossen zu sein.
hasAlternateCompletion
manchmal
boolean
booleanDie Aktivität wurde auf eine Weise beendet, die nicht 100 % erreicht — ein bis zum Ende beantwortetes Quiz, ein gelöstes Lösungsfeld. Behandle das als Abschluss.
missedKeys
manchmal
array
arrayZeichen, die der Spieler immer wieder falsch getippt hat, die häufigsten zuerst. Nur beim Tipptraining.
contentVersion
manchmal
number
numberGegen welche Version des Aktivitätsinhalts das gespielt wurde. Sie ändert sich, wenn der Eigentümer die Fragen bearbeitet, sodass sich ein altes Ergebnis von einem aktuellen unterscheiden lässt.
Ein abgeschlossenes Quiz, so wie es ankommt
{
  "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
}
Fehlend, nicht leer

Ein Feld, das nicht zutrifft, wird aus dem JSON weggelassen, statt als null oder 0 gesendet zu werden. So unterscheidet sich ein Typ, der einen Durchlauf gar nicht bewertet, von einem Durchlauf, der null Punkte erzielt hat — lies also immer mit einem Standardwert und geh nie davon aus, dass ein Schlüssel vorhanden ist.

Die Aktivität von deiner Seite aus zurücksetzen

Eine Anweisung geht den umgekehrten Weg. Ist "Auslöser von der übergeordneten Seite annehmen" aktiviert, kann die einbettende Seite die Antworten des Spielers löschen und die Aktivität auf den Anfang zurücksetzen — für eine eigene "Nochmal versuchen"-Schaltfläche außerhalb des Frames.

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');
Zurücksetzen ist der einzige Auslöser

Es gibt keine Nachricht zum Einreichen eines Ergebnisses, zum Öffnen der Abschlussnachricht oder zum Springen zu einer Frage. Eine Nachricht, die etwas anderes als ein Zurücksetzen verlangt, wird ignoriert.

Nur die einbettende Seite wird gehört

Der Auslöser wird nur von der Seite akzeptiert, die die Aktivität einbettet, und von nirgendwo sonst — kein benachbarter Frame, kein Skript auf der Seite. Die Einstellung ist standardmäßig deaktiviert, also aktivier sie für die Aktivitäten, die du steuerst.

Deine eigene Spieleridentität mitbringen

Wenn deine Plattform schon weiß, wer spielt, muss die Aktivität nicht noch einmal danach fragen. Es gibt zwei Handshakes, beide für eine Aktivität innerhalb deiner Seite, und beide werden von uns aktiviert statt im Editor — sie ändern, wem ein Ergebnis zugeordnet wird, deshalb werden sie gemeinsam mit dir eingerichtet und nicht über eine Checkbox.

Den Spieler an uns senden

Die Aktivität meldet sich mit 'app-loaded' und wartet. Deine Seite sendet die Angaben des Spielers zurück, und das Ergebnis wird unter ihnen abgelegt, ohne dass der Spieler etwas eintippen oder einen Registrierungsbildschirm sehen muss.

Ein Token an uns senden

Derselbe Handshake, aber deine Seite sendet statt der einzelnen Felder das JWT, das dein Identity-Provider ausgestellt hat. Wir prüfen es gegen die für dein Konto eingerichteten Aussteller, bevor der Spieler hereingelassen wird — die Identität ist also nachgewiesen und nicht nur behauptet. Das ist die richtige Wahl, wenn das Ergebnis vertrauenswürdig sein muss.

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();
});
Bitte uns, es zu aktivieren

Sag uns, welche der beiden Varianten du möchtest und wo die Aktivitäten eingebettet werden, und wir richten dein Konto ein und gehen es gemeinsam mit dir durch.

E-Mail zum Thema Identität senden

Aktivitäten aus deinem eigenen System erstellen

Alles oben dreht sich darum, dass ein Ergebnis rauskommt. Der umgekehrte Weg — die Aktivitäten selbst aus Inhalten zu erstellen, die du schon hast — ist die Puzzle-API: ein POST pro Aktivitätstyp, und du bekommst einen Schlüssel und eine URL zum Einbetten zurück.

API-Referenz lesen

Wenn ein fertiger Connector die bessere Antwort ist

Wenn die Plattform auf der anderen Seite ein echtes LMS ist, brauchst du wahrscheinlich nichts von alldem. Noten können von allein ins Notenbuch zurückfließen, ohne dass du etwas hosten musst.

Keins von diesen?

Kursplattformen, Mitgliederseiten, Intranets und alles, was du selbst gebaut hast, sind genau das, wofür die Kanäle auf dieser Seite da sind. Eine Einbettung plus Abschlusssignal deckt das meiste davon ab.

Was es nicht gibt

Damit du nicht danach suchst:

  • Kein Endpunkt zum Auslesen von Ergebnissen. Die API erstellt Aktivitäten; Ergebnisse verlassen das System über die Kanäle auf dieser Seite oder über die Exporte in deinem Dashboard.
  • Keine Signatur beim Webhook. Es gibt nichts, wogegen du die Anfrage prüfen könntest — deshalb sollte ein Ergebnis nie das Einzige sein, was hinter etwas Wichtigem steht.
  • Kein kontoweiter Webhook. Die URL ist eine Einstellung an einer Aktivität — eine kopierte Aktivität übernimmt sie, eine neue startet ohne sie.
  • Nichts bei einem Team-Spiel oder einem Live-Raum. Beide Nachrichtenkanäle und der Webhook sind für Einzelspiel gedacht, und die Einstellungen schalten sich selbst aus, sobald der Team-Modus aktiv ist.
  • Keine Zustellwarteschlange. Nichts wird gespeichert und erneut gesendet — das nächste Speichern ist der Wiederholungsversuch, und der letzte Aufruf des Durchlaufs zählt.

Etwas macht nicht, was es soll?

Schick deine Anfrage und die erhaltene Fehlermeldung. Du bekommst persönlich Hilfe von der Person, die den Endpunkt entwickelt hat.

Support per E-Mail