← Volver a la app

Webhooks

Classroom Analytics te avisa por HTTP cuando ocurren eventos importantes (un scan termina, un pago cambia tu plan, etc.) para que tu sistema reaccione sin tener que consultar nuestra API.

1. Crear un webhook

Iniciá sesión como owner o admin y entrá a Usuarios → sección Webhooks. Pegá tu URL HTTPS, elegí los eventos y guardá. Vas a ver el secreto una sola vez: copialo y guardalo en tu vault.

2. Eventos disponibles

EventoCuándo se disparaPayload
scan.completed Un scan terminó OK. { scan_id, completed_at, total_events }
scan.failed Un scan abortó por error. { scan_id, error, completed_at }
billing.changed Cambió el plan o estado de la suscripción (Stripe). { tier, status, paid_subscription_active, ... }

3. Estructura del POST

El cuerpo es JSON. Headers relevantes:

Ejemplo de body:

{
  "event": "scan.completed",
  "tenant_id": "miescuela_edu_ar",
  "timestamp": 1762800000,
  "data": {
    "scan_id": "scan_abc123",
    "completed_at": "2026-05-13T14:22:11+00:00",
    "total_events": 42
  }
}

4. Verificar la firma

Siempre verificá la firma antes de procesar el body. Calculá HMAC-SHA256 sobre los bytes crudos del request (no sobre el JSON re-serializado).

Node.js / Express

const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.header('X-Classroom-Signature') || '';
    const expected = 'sha256=' + crypto
      .createHmac('sha256', process.env.WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');
    if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
      return res.status(401).send('bad signature');
    }
    const evt = JSON.parse(req.body.toString('utf8'));
    console.log('Event:', evt.event, evt.data);
    res.status(200).send('ok');
  }
);
app.listen(3000);

Python / FastAPI

import hmac, hashlib, os
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
SECRET = os.environ["WEBHOOK_SECRET"].encode()

@app.post("/webhook")
async def webhook(request: Request):
    body = await request.body()
    sig = request.headers.get("x-classroom-signature", "")
    expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, expected):
        raise HTTPException(401, "bad signature")
    evt = await request.json()
    print("Event:", evt["event"], evt["data"])
    return {"ok": True}

5. Reintentos y desactivación

6. Buenas prácticas

7. Probar localmente

Usá webhook.site para inspeccionar entregas, o exponé tu localhost con ngrok http 3000. En la UI tenés un botón para enviar un ping de prueba.

Última actualización: 2026-05-13