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.
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.
| Evento | Cuándo se dispara | Payload |
|---|---|---|
| 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, ... } |
El cuerpo es JSON. Headers relevantes:
Content-Type: application/jsonX-Classroom-Signature: sha256=<hex> — firma HMAC-SHA256 del body con tu secretoX-Classroom-Webhook-Id: wh_<...> — id de tu subscripciónUser-Agent: ClassroomAnalytics-Webhook/1.0Ejemplo 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
}
}
Siempre verificá la firma antes de procesar el body. Calculá HMAC-SHA256 sobre los bytes crudos del request (no sobre el JSON re-serializado).
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);
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}
2xx resetea el contador de fallos.200 apenas validés la firma; encolá el procesamiento real.timingSafeEqual o hmac.compare_digest).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