Ir para o conteúdo
Integrações para programadores

Liga o Puzzel à tua própria plataforma

O Puzzel pode entregar o resultado de um jogador diretamente a outro sistema — a pontuação, até onde chegou, o que respondeu — sem que esse sistema seja um LMS completo. Esta página reúne todos os canais que existem: o que sai, quando sai e o mínimo que precisas de construir para o receber.

Os resultados saem através de
Um webhook, ou uma mensagem para a página que envolve a atividade
A tua página pode
Iniciar sessão do jogador e reiniciar a atividade
Ativado
Por atividade, em Programador no editor
Incluído em
Um plano pago — estas definições estão desativadas numa conta gratuita

De que canal precisas?

Há três coisas que podem sair de uma atividade e uma que pode entrar. A que serve depende de uma única pergunta: o jogador está dentro da tua página, ou completamente noutro lado?

A sair do Puzzel
A entrar no Puzzel

Webhook de resultados

Ativa "Enviar os resultados para um webhook" no editor e indica um URL. A partir daí, sempre que o progresso do jogador é guardado, o navegador dele envia o resultado completo por POST, em JSON, para esse URL.

Como configurar
  1. 1 Abre a atividade no editor e vai ao menu Programador.
  2. 2 Ativa "Enviar os resultados para um webhook" e cola o teu endpoint no campo por baixo. Tem de ser um URL completo — um domínio isolado é recusado — e tem de ser https, porque o navegador bloqueia uma chamada em http simples feita a partir de uma página servida por https.
  3. 3 Joga a atividade uma vez, tu próprio. O primeiro POST chega assim que respondes a alguma coisa.
Um recetor, do início ao fim
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);
});
Dispara enquanto jogas, não só no final

Um resultado sai sempre que a entrada é guardada: depois de uma pausa a escrever, quando uma carta é colocada, quando o cronómetro para, e mais uma vez quando a atividade termina. Umas palavras cruzadas longas geram umas duas dezenas de chamadas, não uma só — por isso escreve o teu recetor como um upsert indexado por playerUid e activityKey, e não como uma inserção. Cada chamada transporta o estado completo, por isso a mais recente substitui sempre a anterior e uma chamada que se perde é compensada pela seguinte.

Distinguir uma conclusão de um simples guardar

progress é uma percentagem: 100 significa que a atividade está completa. Alguns tipos podem terminar sem chegar lá — um quiz respondido até ao fim, um campo de solução resolvido — e esses trazem hasAlternateCompletion em vez disso. Trata qualquer um dos dois casos como concluído.

É enviado pelo navegador do jogador

O POST vem do separador onde a atividade está a ser jogada, não de um servidor do Puzzel. A maioria dos endpoints criados para receber webhooks já aceita isto. Se o teu nunca vê um pedido, é por esta razão: o navegador pede autorização primeiro, por isso responde ao preflight OPTIONS com um cabeçalho Access-Control-Allow-Origin, e o POST verdadeiro segue-se.

Trata o conteúdo enviado como uma afirmação, não uma prova

Não há assinatura no pedido, e este vem de um navegador que não controlas, por isso qualquer pessoa que veja a página também te pode enviar um. Isso não é problema para preencher uma barra de progresso ou um painel. Para algo que não deixarias um aluno definir por si próprio — uma nota que conta, um certificado, um pagamento — confirma-o com os resultados no teu próprio painel do Puzzel, ou deixa que os conectores de notas do LMS transportem a pontuação em vez disso.

Resultados para a página que a envolve

Se incorporares a atividade, podes ter esse mesmo JSON enviado para a tua própria página em vez de para um servidor. Ativa "Enviar os resultados para a página principal" e fica à escuta da mensagem. Nada sai do navegador, por isso não há endpoint para construir nem CORS a considerar.

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>
Confirma de onde veio a mensagem

O teu recetor ouve todas as mensagens enviadas para a página, incluindo de outras frames e extensões do navegador. Compara event.origin com https://puzzel.org antes de confiares no que traz.

O mesmo conteúdo, o mesmo momento

Isto é o gémeo do webhook: os mesmos campos, enviados nos mesmos momentos. Tudo o que está em "O que contém um resultado" também se aplica aqui.

Sinal de conclusão

O canal mais pequeno, para quando o resultado em si não te diz respeito: ativa "Enviar um sinal de conclusão" e a tua página recebe uma mensagem no momento em que o jogador 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);
});
O que chega
{
  "completed": true,
  "activityKey": "-Nq8sample_activity_key"
}
O resultado já está guardado quando chega

O sinal é enviado deliberadamente depois de o guardar de conclusão ter sido concluído, por isso uma página que reaja lendo o resultado de volta vai encontrá-lo lá.

Só dentro de um iframe

Ambos os canais de mensagens enviam para a página que enquadra a atividade. Aberta no seu próprio separador, não há ninguém para avisar, por isso nada é enviado.

O que contém um resultado

Uma única forma, seja qual for o canal que a transporta. As respostas do jogador são indexadas pelos ids dos próprios itens da atividade, por isso as mesmas chaves aparecem em correctUids.

CampoTipoO que faz
activityKey
sempre
string
stringA atividade a que o resultado pertence. A mesma chave que vês no próprio URL da atividade, depois de ?p=.
playerUid
sempre
string
stringQuem jogou, como um id anónimo. Estável para este jogador neste dispositivo, por isso é nele que deves indexar os resultados — não é um endereço de e-mail nem uma conta do Puzzel.
player
por vezes
object
objectOs campos de registo que a atividade pede, tal como os configuraste: nome, e-mail, turma, student_id, e assim por diante. Ausente até o jogador se registar, e ausente por completo numa atividade que não pede nada.
progress
sempre
number
numberAté onde chegou, em percentagem. 100 significa terminado.
timePassed
sempre
number
numberTempo passado na atividade, em milissegundos.
lastPlayedAt
sempre
number
numberQuando este resultado foi guardado, como um timestamp Unix em milissegundos.
createdAt
por vezes
number
numberQuando a tentativa foi iniciada, como um timestamp Unix em milissegundos.
playerInput
por vezes
object
objectO que o jogador realmente introduziu, indexado pelo id do item a que pertence. A forma interna depende do tipo de atividade — uma palavra, uma lista de cartas colocadas, uma opção escolhida.
correctUids
por vezes
object
objectQuais desses itens estão certos, indexados da mesma forma. Ausente enquanto ainda não foi respondido nada.
score
por vezes
number
numberPontos obtidos, nos tipos que pontuam uma partida. Ausente em todos os outros casos — incluindo numa partida que genuinamente pontuou zero, por isso confirma que a chave existe antes de a leres.
performance
por vezes
number
numberA medida própria de um tipo de como correu, quando existe uma — palavras por minuto no treino de teclado, por exemplo.
attempts
por vezes
number
numberQue partida é esta: 1 da primeira vez, mais um a cada recomeço. Só nos tipos que terminam uma partida antecipadamente e contam as repetições.
knockedOut
por vezes
boolean
booleanA partida terminou numa resposta errada e acabou sem estar completa.
hasAlternateCompletion
por vezes
boolean
booleanA atividade foi terminada de uma forma que não chega aos 100% — um quiz respondido até ao fim, um campo de solução resolvido. Trata isto como uma conclusão.
missedKeys
por vezes
array
arrayCarateres que o jogador continuou a errar, começando pelos mais falhados. Só no treino de teclado.
contentVersion
por vezes
number
numberContra que versão do conteúdo da atividade isto foi jogado. Muda quando o proprietário edita as perguntas, por isso um resultado antigo pode ser distinguido de um atual.
Um quiz terminado, tal como chega
{
  "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, não vazio

Um campo que não se aplica é deixado de fora do JSON, em vez de ser enviado como null ou zero. É assim que se distingue um tipo que não pontua uma partida de uma partida que pontuou zero — por isso lê sempre com um valor por omissão e nunca assumas que uma chave está presente.

Reiniciar a atividade a partir da tua página

Uma instrução viaja no sentido contrário. Com "Aceitar comandos da página principal" ativado, a página que faz a incorporação pode limpar as respostas do jogador e voltar a colocar a atividade no início — para um botão "tentar novamente" teu, fora da frame.

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 é o único comando

Não existe mensagem para submeter um resultado, abrir a mensagem final, ou saltar para uma pergunta. Uma mensagem que peça outra coisa que não um reinício é ignorada.

Só a página de incorporação é ouvida

O comando só é aceite a partir da página que enquadra a atividade e de mais lado nenhum — nem uma frame irmã, nem um script na página. A definição está desativada por omissão, por isso ativa-a nas atividades que controlas.

Trazer a tua própria identidade de jogador

Se a tua plataforma já sabe quem está a jogar, a atividade não precisa de perguntar outra vez. Existem dois handshakes, ambos para uma atividade dentro da tua página, e ambos ativados por nós em vez de no editor — mudam a quem pertence um resultado, por isso são configurados contigo em vez de a partir de uma caixa de verificação.

Envia-nos o jogador

A atividade anuncia-se com 'app-loaded' e espera. A tua página envia de volta os dados do jogador, e o resultado fica associado a esses dados sem que o jogador escreva nada ou veja um ecrã de registo.

Envia-nos um token

O mesmo handshake, mas a tua página envia o JWT emitido pelo teu fornecedor de identidade em vez dos próprios campos. Verificamo-lo em relação aos emissores configurados para a tua conta antes de o jogador ser deixado entrar, por isso a identidade é comprovada e não apenas declarada — este é o que deves pedir quando o resultado tem de ser fiável.

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();
});
Pede-nos para o ativar

Diz-nos qual dos dois queres e onde as atividades vão ser incorporadas, e nós configuramos a tua conta e explicamos-te o processo passo a passo.

Envia-nos um e-mail sobre identidade

Criar atividades a partir do teu sistema

Tudo o que foi dito acima é sobre um resultado a sair. No sentido contrário — criar as próprias atividades a partir de conteúdo que já tens — está a Puzzle API: um POST por tipo de atividade, e recebes de volta uma chave e um URL para incorporar.

Ler a referência da API

Quando um conector já feito é a melhor resposta

Se a plataforma do outro lado for um LMS a sério, provavelmente não precisas de nada disto. As notas podem voltar sozinhas para o livro de notas, sem nada que precises de alojar.

Não é nenhum destes?

Plataformas de cursos, sites de membros, intranets e qualquer coisa que tenhas construído tu próprio são exatamente para isso que servem os canais desta página. Uma incorporação mais o sinal de conclusão cobre a maior parte dos casos.

O que não existe

Para não andares à procura disso:

  • Não há endpoint para ler resultados de volta. A API cria atividades; os resultados saem através dos canais desta página, ou através das exportações no teu painel.
  • Não há assinatura no webhook. Não há nada contra o qual verificar o pedido, e é por isso que um resultado não deve ser a única coisa a sustentar algo que importa.
  • Não há webhook à escala da conta. O URL é uma definição de uma atividade, por isso uma atividade que copias transporta-o consigo e uma nova começa sem ele.
  • Nada num jogo em equipa ou numa sala ao vivo. Tanto os canais de mensagens como o webhook são para jogo a solo, e as definições desativam-se sozinhas quando o modo de equipas está ativado.
  • Não há fila de entrega. Nada é guardado e reenviado — o próximo guardar é a nova tentativa, e a última chamada da partida é a que conta.

Algo não está a funcionar bem?

Envia o pedido que tentaste e o erro que recebeste e terás uma resposta a sério, de quem escreveu o endpoint.

Enviar e-mail ao suporte