Un webhook es una dirección de tu servidor a la que Jazui hace un POST con un JSON cuando pasa algo. Hay dos por sitio: el del blog (artículos publicados, actualizados o retirados) y el de alertas (errores de render, caída de Googlebot, bajada de la nota SEO). Los dos se firman con el mismo secreto del sitio.
Configurarlos
En el panel, abre los ajustes del sitio:
- Blog e IndexNow: dirección del webhook del blog. Con Volver a renderizar la página del artículo (activado por defecto), Jazui además actualiza la página del artículo en la caché.
- Alertas: dirección del webhook de alertas (si tu plan incluye webhooks de alertas).
La dirección tiene que ser pública y con https o http. Al guardar el primer webhook se genera el secreto del sitio (32 caracteres), que verás en los ajustes. Con Probar (blog) recibes un evento ping, y con Enviar alerta de prueba (alertas), una alerta de tipo Test.
Cómo llegan
Un POST con Content-Type: application/json y estas cabeceras:
X-Jazui-Event: el tipo (Published,Updated,Unpublished,alertoping).X-Jazui-Delivery: identificador del envío, el mismo en los reintentos (úsalo para no procesar dos veces el mismo).X-Jazui-Signature:sha256=seguido del HMAC-SHA256 del cuerpo con tu secreto, en hexadecimal en minúsculas.
POST https://misitio.com/webhooks/jazui
Content-Type: application/json
X-Jazui-Event: Published
X-Jazui-Delivery: 3f0c9a3e-7a51-4f43-9c1e-0b8d2f6a4e11
X-Jazui-Signature: sha256=5d41402abc4b2a76b9719d911017c592ae8b6e1f0a2c4d3b5e7f9a1c3d5e7f90Eventos del blog
event vale article.published, article.updated o article.unpublished. El mismo artículo con el mismo evento no se envía dos veces en 30 segundos. Úsalo para regenerar tu web estática, avisar a tu equipo o publicar en otros sitios.
{
"event": "article.published",
"site": "misitio.com",
"occurredAt": "2026-11-03T00:47:24Z",
"article": {
"id": "7c1d0e2f-…",
"slug": "como-funciona-el-prerender",
"url": "https://misitio.com/blog/como-funciona-el-prerender",
"language": "es",
"title": "Cómo funciona el prerender"
}
}Alertas
alert.type vale RenderErrors (errores de render en 24 h), GoogleDrop (caída de las visitas de Googlebot frente a la semana anterior), SeoDrop (bajada de la nota SEO en 7 días) o Test. value es lo medido y threshold el umbral que configuraste.
{
"event": "site.alert",
"site": "misitio.com",
"occurredAt": "2026-11-03T09:00:00Z",
"alert": {
"type": "SeoDrop",
"message": "La nota SEO media de misitio.com ha bajado 12 puntos en 7 días.",
"value": 12,
"threshold": 10,
"detail": "90 → 78"
}
}Prueba
El botón de prueba del webhook del blog envía:
{ "event": "ping", "site": "misitio.com", "occurredAt": "2026-11-03T09:00:00Z" }Comprobar la firma
Calcula el HMAC-SHA256 del cuerpo tal como llega (sin volver a serializarlo) con tu secreto y compáralo con la cabecera en tiempo constante. Si no coincide, responde 401 y no lo proceses.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/webhooks/jazui', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', process.env.JAZUI_WEBHOOK_SECRET)
.update(req.body).digest('hex');
const received = req.get('X-Jazui-Signature') || '';
if (expected.length !== received.length ||
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
// event.event: article.published, article.updated, article.unpublished, site.alert o ping
res.sendStatus(204);
});app.MapPost("/webhooks/jazui", async (HttpRequest request, IConfiguration config) =>
{
using var reader = new StreamReader(request.Body, Encoding.UTF8);
var body = await reader.ReadToEndAsync();
var key = Encoding.UTF8.GetBytes(config["Jazui:WebhookSecret"]!);
var expected = "sha256=" + Convert.ToHexString(HMACSHA256.HashData(key, Encoding.UTF8.GetBytes(body))).ToLowerInvariant();
var received = request.Headers["X-Jazui-Signature"].ToString();
if (!CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(received)))
return Results.Unauthorized();
using var json = JsonDocument.Parse(body);
var type = json.RootElement.GetProperty("event").GetString();
return Results.NoContent();
});Respuestas y reintentos
- Cualquier respuesta por debajo de
500se da por entregada: responde2xxen cuanto lo recibas y haz el trabajo después. - Con un
5xxo sin respuesta, Jazui lo reintenta a los 2 y a los 10 segundos (tres intentos en total). - Cada intento espera como mucho 15 segundos y sigue hasta 3 redirecciones.