API

Construye sobre Qwizbee

Que tu app sepa al instante cuándo empieza o termina un quiz. Los usuarios conectan con un clic, tú recibes webhooks firmados y nadie comparte nunca una contraseña ni un token de cuenta.

https://qwizbee-api-production.up.railway.app

Cómo funciona la conexión

Tres pasos, sin portal de desarrolladores ni registro previo. Tu app genera un verificador PKCE, el usuario aprueba una vez y tú guardas la clave API que vuelve.

  1. Añade un botón Conectar

    Envía al usuario a qwizbee.com/connect con el nombre de tu app, una URI de redirección https, un desafío PKCE y un valor state aleatorio.

  2. El usuario aprueba

    Qwizbee muestra una página de consentimiento en el idioma del usuario. Tras Permitir, el navegador vuelve a tu URI de redirección con un código de un solo uso (válido 2 minutos) y tu state.

  3. Canjea el código

    Tu servidor cambia el código más el verificador PKCE en POST /api/integrations/token por una clave API qbw_…. Esa clave solo gestiona webhooks; nada más de la cuenta.

El enlace de conexión

Genera el verificador y el desafío en tu servidor y guarda el verificador en la sesión del usuario:

// on your server
const verifier  = base64url(crypto.randomBytes(32));
const challenge = base64url(sha256(verifier));

// your "Connect to Qwizbee" button points at:
https://www.qwizbee.com/connect
  ?name=Supergood Mail
  &redirect_uri=https://www.supergood-mail.com/callback
  &code_challenge=${challenge}
  &state=${randomState}

// after the user approves you are called back at:
https://www.supergood-mail.com/callback?code=${code}&state=${randomState}

El valor state vuelve intacto para protegerte del CSRF, y la URI de redirección debe ser https (http://localhost se permite para pruebas).

// exchange the code for the scoped API key
const res = await fetch('https://qwizbee-api-production.up.railway.app/api/integrations/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ code, code_verifier: verifier }),
});
const { access_token } = await res.json(); // qbw_… — store it safely

Los eventos

Tras conectar, registra un webhook con la clave API. Desde entonces Qwizbee envía por POST un mensaje JSON firmado a tu URL cada vez que un quiz del usuario empieza o termina.

quiz.started

Se dispara cuando el host pulsa Empezar.

POST https://your-app.com/webhooks/qwizbee
X-Qwizbee-Event: quiz.started
X-Qwizbee-Signature: <hmac-sha256>

{
  "event": "quiz.started",
  "timestamp": "2026-10-08T13:05:22.410Z",
  "data": {
    "sessionId": "s_ab12cd",
    "roomCode": "KGMZ34",
    "quizId": "qz_9x8y",
    "quizTitle": "Friday afternoon quiz",
    "playerCount": 14,
    "questionCount": 10
  }
}

quiz.completed

Se dispara cuando aparecen las posiciones finales. Añade al ganador.

{
  "event": "quiz.completed",
  "timestamp": "2026-10-08T13:12:40.911Z",
  "data": {
    ...same fields as quiz.started,
    "winner": { "nickname": "BeeMercia", "score": 8420 }
  }
}

Campos del payload

event
quiz.started o quiz.completed
timestamp
momento del evento en ISO 8601
data.sessionId
id interno de sesión, estable por quiz
data.roomCode
el código de sala de 6 caracteres con el que entran los jugadores
data.quizId / quizTitle
qué quiz era
data.playerCount / questionCount
cuántos jugadores y preguntas
data.winner
solo en quiz.completed: nickname y puntuación del número 1, o null

Verificar la firma

Cada entrega se firma con el secret propio del webhook (se muestra una vez al registrarlo). Verifica el cuerpo bruto de la petición, no un objeto re-serializado, antes de confiar en una entrega:

import { createHmac, timingSafeEqual } from 'node:crypto';

app.post('/webhooks/qwizbee', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex');
  const got = req.headers['x-qwizbee-signature'] || '';
  const valid = expected.length === got.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(got));
  if (!valid) return res.status(401).end();

  const event = JSON.parse(req.body);
  if (event.event === 'quiz.started') { /* … */ }
  res.status(200).end();
});

Referencia de la API

URL base de la API de producción. JWT significa el token de sesión del usuario; clave API significa una clave qbw_… del flujo de conexión. Las rutas de webhooks aceptan ambas.

Flujo de conexión

  • GET/api/connect/authorizeJWT

    Valida los parámetros de conexión; devuelve el nombre de la app para la página de consentimiento.

  • POST/api/connect/approveJWT

    Aprobar y recibir la URL de redirección con el código de un solo uso y el state.

  • POST/api/integrations/tokenpublic

    Canjear código + code_verifier por la clave API aislada (qbw_…).

  • GET/api/integrationsJWT

    Las apps conectadas del usuario con marcas de último uso.

  • DELETE/api/integrations/:idJWT

    Cortar la conexión. Sus webhooks se borran al instante y la clave deja de funcionar.

Webhooks

  • POST/api/webhooksJWT o clave API

    Registrar un webhook: { url, events }. La respuesta contiene el secret de firma, visible solo una vez.

  • GET/api/webhooksJWT o clave API

    Listar los webhooks registrados.

  • DELETE/api/webhooks/:idJWT o clave API

    Eliminar un webhook.

  • GET/api/webhooks/:id/deliveriesJWT o clave API

    Los últimos 50 intentos de entrega con códigos de estado, para depurar.

Comportamiento de entrega

  • Las entregas fallidas se reintentan tras 1, 5 y 25 segundos (4 intentos en total).
  • Tu endpoint debe responder en 5 segundos con cualquier estado 2xx.
  • Los errores de cliente (4xx) no se reintentan: rechazaste claramente, así que paramos.
  • Cada intento queda registrado y visible vía el endpoint de entregas.
  • Cortar la conexión en la página Cuenta del usuario detiene todas las entregas al instante.

Probar tu integración

  • Registra un webhook hacia una URL de https://webhook.site para inspeccionar entregas reales.
  • http://localhost y http://127.0.0.1 se aceptan como destino de webhook y de redirección, así que puedes desarrollar en local.
  • El registro de entregas (GET /api/webhooks/:id/deliveries) muestra los códigos de estado exactos que recibimos de ti.

¿Preguntas o falta un evento?

contact@kylevanraaij.com Escribe un correo y cuéntanos qué construyes: los eventos por jugador están en la hoja de ruta.