Pular para o conteúdo
Integrações para desenvolvedores

Conecte o Puzzel à sua própria plataforma

O Puzzel pode enviar o resultado de um jogador diretamente para outro sistema — a pontuação, até onde ele chegou, o que respondeu — sem que esse sistema precise ser um LMS completo. Esta página reúne todos os canais que existem: o que sai, quando sai e o mínimo que você precisa construir para recebê-lo.

Os resultados saem via
Um webhook ou uma mensagem para a página que envolve a atividade
Sua página pode
Fazer login do jogador e reiniciar a atividade
Ativado
Por atividade, em Desenvolvedor, no editor
Incluído em
Um plano pago — essas configurações ficam desativadas em uma conta gratuita

De qual canal você precisa?

Três coisas podem sair de uma atividade e uma pode entrar. Qual delas serve depende de uma única pergunta: o jogador está dentro da sua página ou em outro lugar completamente diferente?

Saindo do Puzzel
Entrando no Puzzel

Webhook de resultados

Ative "Enviar resultados para um webhook" no editor e informe uma URL. A partir daí, toda vez que o progresso do jogador for salvo, o navegador dele envia por POST o resultado inteiro para essa URL, como JSON.

Como configurar
  1. 1 Abra a atividade no editor e vá até o menu Desenvolvedor.
  2. 2 Ative "Enviar resultados para um webhook" e cole seu endpoint no campo logo abaixo. Precisa ser uma URL completa — um domínio isolado é recusado — e precisa ser https, porque o navegador bloqueia uma chamada http simples feita a partir de uma página servida via https.
  3. 3 Jogue a atividade uma vez você mesmo. O primeiro POST chega assim que você responder alguma coisa.
Um receptor, 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);
});
Ele dispara enquanto você joga, não só no final

Um resultado sai toda vez que a entrada é salva: depois de uma pausa na digitação, quando uma carta é posicionada, quando o cronômetro para, e mais uma vez quando a atividade termina. Uma grade de palavras cruzadas longa gera algumas dezenas de chamadas, não uma só — por isso escreva seu handler como um upsert com chave em playerUid e activityKey, em vez de um insert. Cada chamada carrega o estado completo, então a mais recente sempre substitui a anterior e uma chamada perdida é compensada pela próxima.

Diferenciar um término de um salvamento

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

É enviado pelo navegador do jogador

O POST vem da aba em que a atividade está sendo jogada, não de um servidor do Puzzel. A maioria dos endpoints criados para receber webhooks já aceita isso. Se o seu nunca recebe uma requisição, é por isso: o navegador pede permissão primeiro, então responda ao preflight OPTIONS com um cabeçalho Access-Control-Allow-Origin e o POST de verdade vem em seguida.

Trate o payload como uma alegação, não uma prova

Não há assinatura na requisição, e ela vem de um navegador que você não controla, então qualquer pessoa que olhar a página também pode te enviar uma. Isso é tranquilo para preencher uma barra de progresso ou um painel. Para qualquer coisa que você não deixaria um aluno definir sozinho — uma nota que conta, um certificado, um pagamento — confira com os resultados no seu próprio painel do Puzzel, ou deixe que os conectores de nota do LMS carreguem a pontuação em vez disso.

Resultados para a página ao redor

Se você incorporar a atividade, pode fazer com que esse mesmo JSON seja enviado para a sua própria página em vez de para um servidor. Ative "Enviar resultados para a página pai" e escute a mensagem. Nada sai do navegador, então não há endpoint para construir nem CORS para se preocupar.

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

Seu listener escuta toda mensagem enviada para a página, incluindo de outros frames e extensões do navegador. Compare event.origin com https://puzzel.org antes de confiar no que está nela.

O mesmo payload, o mesmo momento

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

Sinal de conclusão

O menor canal, para quando o resultado em si não é da sua conta: ative "Enviar um sinal de conclusão" e sua página recebe uma mensagem no exato 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á salvo quando ela chega

O sinal é enviado propositalmente depois que o salvamento de conclusão já foi concretizado, então uma página que reage lendo o resultado de volta vai encontrá-lo lá.

Somente dentro de um iframe

Os dois canais de mensagem enviam para a página que enquadra a atividade. Aberta em sua própria aba, não há ninguém para avisar, então nada é enviado.

O que um resultado contém

Um único formato, seja qual for o canal que o carrega. As respostas do jogador são indexadas pelos próprios ids dos itens da atividade, então as mesmas chaves aparecem em correctUids.

CampoTipoO que faz
activityKey
sempre
string
stringA atividade à qual o resultado pertence. A mesma chave que você vê na própria URL da atividade, depois de ?p=.
playerUid
sempre
string
stringQuem jogou, como um id anônimo. Estável para esse jogador nesse dispositivo, então é nele que você deve basear a chave dos resultados — não é um endereço de e-mail nem uma conta do Puzzel.
player
às vezes
object
objectOs campos de cadastro que a atividade pede, conforme você os configurou: name, email, class, student_id e assim por diante. Ausente até o jogador se cadastrar, e totalmente ausente em uma atividade que não pede nada.
progress
sempre
number
numberAté onde chegou, em porcentagem. 100 significa finalizado.
timePassed
sempre
number
numberTempo na atividade, em milissegundos.
lastPlayedAt
sempre
number
numberQuando esse resultado foi salvo, como um timestamp Unix em milissegundos.
createdAt
às vezes
number
numberQuando a tentativa foi iniciada, como um timestamp Unix em milissegundos.
playerInput
às vezes
object
objectO que o jogador realmente digitou, indexado pelo id do item ao qual pertence. O formato interno depende do tipo de atividade — uma palavra, uma lista de cartas posicionadas, uma opção escolhida.
correctUids
às vezes
object
objectQuais desses itens estão certos, indexados da mesma forma. Ausente enquanto nada foi respondido ainda.
score
às vezes
number
numberPontos obtidos, nos tipos que pontuam uma tentativa. Ausente em todos os outros — inclusive em uma tentativa que realmente pontuou zero, então verifique se a chave existe antes de lê-la.
performance
às vezes
number
numberA própria medida de desempenho de um tipo, quando ele mantém uma — palavras por minuto no exercício de digitação, por exemplo.
attempts
às vezes
number
numberQual tentativa é essa: 1 na primeira vez, mais uma a cada novo começo. Só nos tipos que encerram uma tentativa antes da hora e contam as repetições.
knockedOut
às vezes
boolean
booleanA tentativa terminou em uma resposta errada e acabou sem estar completa.
hasAlternateCompletion
às vezes
boolean
booleanA atividade foi finalizada de um jeito que não chega a 100% — um quiz respondido até o fim, um campo de solução resolvido. Trate isso como uma conclusão.
missedKeys
às vezes
array
arrayCaracteres que o jogador continuou errando, do mais errado para o menos errado. Somente no exercício de digitação.
contentVersion
às vezes
number
numberContra qual versão do conteúdo da atividade isso foi jogado. Ela muda quando o proprietário edita as perguntas, então um resultado antigo pode ser diferenciado de um atual.
Um quiz finalizado, como ele 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 um tipo que não pontua uma tentativa se diferencia de uma tentativa que pontuou zero — então leia com um valor padrão e nunca presuma que uma chave está presente.

Reiniciando a atividade a partir da sua página

Uma instrução viaja no sentido contrário. Com "Aceitar gatilhos da página pai" ativado, a página que faz a incorporação pode limpar as respostas do jogador e colocar a atividade de volta no início — para um botão "tentar de novo" seu, fora do 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 gatilho

Não existe mensagem para enviar um resultado, abrir a mensagem final ou pular para uma pergunta. Uma mensagem pedindo qualquer coisa além de um reinício é ignorada.

Só a página que incorpora é ouvida

O gatilho só é aceito vindo da página que enquadra a atividade e de nenhum outro lugar — nem um frame irmão, nem um script na página. A configuração vem desativada por padrão, então ative-a para as atividades que você controla.

Trazendo sua própria identidade de jogador

Se sua plataforma já sabe quem está jogando, a atividade não precisa perguntar de novo. Existem dois handshakes, ambos para uma atividade dentro da sua página, e ambos ativados por nós em vez de no editor — eles mudam a quem um resultado pertence, então são configurados junto com você, e não por uma caixa de seleção.

Envie o jogador para nós

A atividade se anuncia com 'app-loaded' e espera. Sua página envia de volta os dados do jogador, e o resultado é registrado sob eles sem que o jogador digite nada ou veja uma tela de cadastro.

Envie um token para nós

O mesmo handshake, mas sua página envia o JWT emitido pelo seu provedor de identidade em vez dos próprios campos. Nós o verificamos contra os emissores configurados para sua conta antes de deixar o jogador entrar, então a identidade é comprovada em vez de apenas declarada — esse é o handshake certo para pedir quando o resultado precisa ser confiá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();
});
Peça para ativarmos

Diga qual dos dois você quer e onde as atividades serão incorporadas, e nós configuramos sua conta e explicamos tudo com você.

Enviar e-mail sobre identidade

Criando atividades a partir do seu sistema

Tudo acima é sobre um resultado saindo. Na direção oposta — criar as próprias atividades a partir de um conteúdo que você já tem — está a Puzzle API: um POST por tipo de atividade, e você recebe de volta uma chave e uma URL para incorporar.

Ler a referência da API

Quando um conector pronto é a resposta melhor

Se a plataforma do outro lado é um LMS de verdade, provavelmente você não precisa de nada disso. As notas podem voltar sozinhas para o diário de classe dele, sem nada para você hospedar.

Não é nenhum desses?

Plataformas de curso, sites de associados, intranets e qualquer coisa que você mesmo construiu são exatamente para o que servem os canais desta página. Uma incorporação mais o sinal de conclusão cobre a maior parte disso.

O que não existe

Para você não sair procurando por isso:

  • Nenhum endpoint para ler resultados de volta. A API cria atividades; os resultados saem pelos canais desta página, ou pelas exportações no seu painel.
  • Nenhuma assinatura no webhook. Não há nada contra o que verificar a requisição, por isso um resultado não deveria ser a única coisa sustentando algo importante.
  • Nenhum webhook para a conta inteira. A URL é uma configuração da atividade, então uma atividade que você copia leva a configuração junto, e uma nova começa sem ela.
  • Nada em um jogo em equipe ou em uma sala ao vivo. Tanto os canais de mensagem quanto o webhook são para o jogo individual, e as configurações se desativam sozinhas quando o modo em equipe está ativado.
  • Nenhuma fila de entrega. Nada é armazenado e reenviado — o próximo salvamento é a nova tentativa, e a última chamada da tentativa é a que importa.

Algo não está se comportando como deveria?

Envie a requisição que você tentou e o erro que recebeu de volta, e você terá uma resposta de verdade, da pessoa que escreveu o endpoint.

E-mail para suporte