Saltar al contenido
Integraciones para desarrolladores

Conecta Puzzel a tu propia plataforma

Puzzel puede entregar el resultado de un jugador directamente a otro sistema —su puntuación, hasta dónde llegó, qué respondió— sin que ese sistema tenga que ser un LMS completo. Esta página reúne todos los canales que existen: qué sale, cuándo sale y lo mínimo que tienes que construir para recibirlo.

Los resultados salen mediante
Un webhook, o un mensaje a la página que rodea la actividad
Tu página puede
Iniciar la sesión del jugador y reiniciar la actividad
Se activa
Por actividad, en Desarrollador dentro del editor
Incluido con
Un plan de pago — estos ajustes están desactivados en una cuenta gratis

¿Qué canal necesitas?

De una actividad pueden salir tres cosas, y puede entrar una. Cuál te conviene depende de una sola pregunta: ¿el jugador está dentro de tu página, o en un sitio completamente distinto?

Sale de Puzzel
Entra en Puzzel

Webhook de resultados

Activa «Enviar los resultados a un webhook» en el editor y dale una URL. A partir de ahí, cada vez que se guarda el progreso del jugador, su navegador envía por POST el resultado completo a esa URL como JSON.

Configurarlo
  1. 1 Abre la actividad en el editor y ve al menú Desarrollador.
  2. 2 Activa «Enviar los resultados a un webhook» y pega tu endpoint en el campo de debajo. Tiene que ser una URL completa —un dominio sin más se rechaza— y tiene que ser https, porque el navegador bloquea una llamada http sin más hecha desde una página servida por https.
  3. 3 Juega la actividad una vez tú mismo. El primer POST llega en cuanto respondes algo.
Un receptor, de principio a fin
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 dispara mientras juegas, no solo al final

Sale un resultado cada vez que se guarda la entrada: tras una pausa al escribir, cuando se coloca una carta, cuando el reloj se detiene, y una vez más cuando termina la actividad. Un crucigrama largo son varias decenas de llamadas, no una sola —así que escribe tu handler como un upsert con clave en playerUid y activityKey, no como una inserción. Cada llamada lleva el estado completo, así que la más reciente siempre reemplaza a la anterior, y una llamada que se pierde queda cubierta por la siguiente.

Distinguir un final de un simple guardado

progress es un porcentaje: 100 significa que la actividad está completa. Algunos tipos pueden terminar sin llegar a ese valor —un quiz respondido hasta el final, un campo de solución resuelto— y en su lugar llevan hasAlternateCompletion. Trata cualquiera de los dos como terminado.

Lo envía el navegador del jugador

El POST llega desde la pestaña donde se está jugando la actividad, no desde un servidor de Puzzel. La mayoría de los endpoints preparados para recibir webhooks ya lo aceptan así. Si el tuyo nunca ve ninguna solicitud, esta es la razón: el navegador pide permiso antes, así que responde a la solicitud OPTIONS previa con una cabecera Access-Control-Allow-Origin, y el POST real llega después.

Trata el payload como una afirmación, no como una prueba

La solicitud no lleva firma, y viene de un navegador que no controlas, así que cualquiera que mire la página puede enviarte una también. Eso está bien para rellenar una barra de progreso o un panel. Para cualquier cosa que no dejarías fijar a un estudiante por su cuenta —una calificación que cuenta, un certificado, un pago— compáralo con los resultados de tu propio panel de Puzzel, o deja que los conectores de calificación del LMS se encarguen de la puntuación.

Resultados a la página que la rodea

Si insertas la actividad, puedes hacer que ese mismo JSON se envíe a tu propia página en lugar de a un servidor. Activa «Enviar los resultados a la página principal» y escucha el mensaje. Nada sale del navegador, así que no hay ningún endpoint que construir ni CORS del que preocuparse.

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>
Comprueba de dónde viene el mensaje

Tu listener recibe todos los mensajes enviados a la página, incluidos los de otros marcos y extensiones del navegador. Compara event.origin con https://puzzel.org antes de confiar en su contenido.

El mismo payload, el mismo momento

Este es el gemelo del webhook: los mismos campos, enviados en los mismos momentos. Todo lo que dice «Qué contiene un resultado» se aplica también aquí.

Señal de finalización

El canal más pequeño, para cuando el resultado en sí no es asunto tuyo: activa «Enviar una señal de finalización» y tu página recibe un mensaje en el momento en que el jugador termina.

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);
});
Qué llega
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
El resultado ya está guardado cuando llega

La señal se envía deliberadamente después de que se haya completado el guardado final, así que una página que reaccione leyendo de vuelta el resultado lo encontrará ahí.

Solo dentro de un iframe

Ambos canales de mensajes envían a la página que enmarca la actividad. Abierta en su propia pestaña no hay nadie a quien avisar, así que no se envía nada.

Qué contiene un resultado

Una sola forma, sea cual sea el canal que la lleve. Las respuestas del jugador tienen como clave los ids propios de cada elemento de la actividad, así que esas mismas claves aparecen en correctUids.

CampoTipoQué hace
activityKey
siempre
string
stringLa actividad a la que pertenece el resultado. La misma clave que ves en la URL propia de la actividad, después de ?p=.
playerUid
siempre
string
stringQuién jugó, como id anónimo. Es estable para este jugador en este dispositivo, así que es lo que debes usar como clave de los resultados —no es una dirección de correo ni una cuenta de Puzzel.
player
a veces
object
objectLos campos de registro que pide la actividad, tal como los configuraste: nombre, correo, clase, student_id, etc. Está ausente hasta que el jugador se registra, y ausente por completo en una actividad que no pide nada.
progress
siempre
number
numberHasta dónde ha llegado, como porcentaje. 100 significa terminado.
timePassed
siempre
number
numberTiempo en la actividad, en milisegundos.
lastPlayedAt
siempre
number
numberCuándo se guardó este resultado, como marca de tiempo Unix en milisegundos.
createdAt
a veces
number
numberCuándo empezó el intento, como marca de tiempo Unix en milisegundos.
playerInput
a veces
object
objectLo que el jugador introdujo realmente, indexado por el id del elemento al que pertenece. La forma interna depende del tipo de actividad —una palabra, una lista de cartas colocadas, una opción elegida.
correctUids
a veces
object
objectCuáles de esos elementos son correctos, con la misma clave. Está ausente mientras no se haya respondido nada todavía.
score
a veces
number
numberPuntos obtenidos, en los tipos que puntúan una partida. Está ausente en el resto de casos —incluida una partida que realmente obtuvo cero puntos, así que comprueba que la clave existe antes de leerla.
performance
a veces
number
numberLa medida propia de cada tipo de cómo le fue, cuando la tiene —palabras por minuto en práctica de mecanografía, por ejemplo.
attempts
a veces
number
numberQué número de partida es: 1 la primera vez, uno más en cada reinicio. Solo en los tipos que pueden terminar una partida antes de tiempo y cuentan los reintentos.
knockedOut
a veces
boolean
booleanLa partida terminó con una respuesta incorrecta y acabó sin completarse.
hasAlternateCompletion
a veces
boolean
booleanLa actividad se terminó de una forma que no llega al 100 % —un quiz respondido hasta el final, un campo de solución resuelto. Trátalo como una finalización.
missedKeys
a veces
array
arrayCaracteres que el jugador seguía fallando, empezando por los más fallados. Solo en práctica de mecanografía.
contentVersion
a veces
number
numberCon qué versión del contenido de la actividad se jugó. Cambia cuando el propietario edita las preguntas, así que un resultado antiguo se puede distinguir de uno actual.
Un quiz terminado, tal como llega
{
  "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
}
Ausente, no vacío

Un campo que no aplica se omite del JSON en lugar de enviarse como null o cero. Así se distingue un tipo que no puntúa una partida de una partida que obtuvo cero puntos —así que léelo con un valor por defecto y nunca des por hecho que la clave está presente.

Reiniciar la actividad desde tu página

Una instrucción viaja en sentido contrario. Con «Aceptar órdenes de la página principal» activado, la página que hace la inserción puede borrar las respuestas del jugador y volver a poner la actividad al principio —para un botón «intentar de nuevo» propio, fuera del marco.

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');
Reiniciar es la única orden

No existe ningún mensaje para enviar un resultado, abrir el mensaje final o saltar a una pregunta. Un mensaje que pida cualquier cosa que no sea un reinicio se ignora.

Solo se escucha a la página que inserta

La orden solo se acepta desde la página que enmarca la actividad y de ningún otro sitio —ni un marco hermano, ni un script de la página. El ajuste está desactivado por defecto, así que actívalo en las actividades que controles tú.

Aportar tu propia identidad de jugador

Si tu plataforma ya sabe quién está jugando, la actividad no tiene que volver a preguntárselo. Existen dos protocolos de enlace, ambos para una actividad dentro de tu página, y ambos los activamos nosotros en lugar del editor —cambian a quién pertenece un resultado, así que se configuran contigo y no desde una casilla.

Envíanos el jugador

La actividad se anuncia con 'app-loaded' y espera. Tu página envía de vuelta los datos del jugador, y el resultado queda archivado a su nombre sin que el jugador escriba nada ni vea una pantalla de registro.

Envíanos un token

El mismo protocolo, pero tu página envía el JWT emitido por tu proveedor de identidad en lugar de los campos en sí. Lo verificamos contra los emisores configurados para tu cuenta antes de dejar entrar al jugador, así que la identidad queda demostrada y no solo declarada —este es el que hay que pedir cuando el resultado tiene que ser fiable.

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();
});
Pídenos que lo activemos

Dinos cuál de los dos quieres y dónde se insertarán las actividades, y configuraremos tu cuenta y te acompañaremos en el proceso.

Escríbenos sobre identidad

Crear actividades desde tu sistema

Todo lo anterior trata de un resultado que sale. En sentido contrario —crear las actividades a partir de contenido que ya tienes— está la Puzzle API: un POST por tipo de actividad, y recibes de vuelta una clave y una URL para insertar.

Leer la referencia de la API

Cuando un conector ya hecho es la mejor opción

Si la plataforma del otro lado es un LMS de verdad, probablemente no necesites nada de esto. Las calificaciones pueden volver solas a su libro de calificaciones, sin que tengas que alojar nada.

¿No es ninguno de esos?

Las plataformas de cursos, los sitios de membresía, las intranets y cualquier cosa que hayas construido tú mismo son exactamente para lo que sirven los canales de esta página. Una inserción más la señal de finalización cubre la mayoría de los casos.

Lo que no hay

Para que no lo busques donde no está:

  • No hay ningún endpoint para leer los resultados de vuelta. La API crea actividades; los resultados salen por los canales de esta página, o por las exportaciones de tu panel.
  • El webhook no lleva firma. No hay nada contra lo que verificar la solicitud, por eso un resultado no debería ser lo único que respalde algo importante.
  • No hay ningún webhook a nivel de cuenta. La URL es un ajuste de cada actividad, así que una actividad que copias lo lleva consigo y una nueva empieza sin él.
  • Nada en un juego por equipos o una sala en directo. Tanto los canales de mensajes como el webhook son para juego individual, y los ajustes se desactivan solos cuando el juego por equipos está activado.
  • No hay ninguna cola de entrega. Nada se almacena ni se reenvía —el siguiente guardado es el reintento, y la última llamada de la partida es la que cuenta.

¿Algo no funciona como debería?

Envíanos la petición que has usado y el error que recibiste. Te responderá la persona que creó la API.

Escribir a soporte