# Webhooks > Jazui notifies your server when you publish an article or a site alert fires. Source page: https://docs.jazui.com/api/webhooks/en A webhook is an address on your server where Jazui sends a `POST` with JSON when something happens. There are two per site: the **blog** one (articles published, updated or unpublished) and the **alerts** one (render errors, Googlebot drop, SEO score drop). Both are signed with the same site secret. ## Setting them up In the dashboard, open the site settings: - **Blog and IndexNow**: blog webhook address. With **Render the article page again** (on by default), Jazui also refreshes the article page in the cache. - **Alerts**: alerts webhook address (if your plan includes alert webhooks). The address must be public, over `https` or `http`. Saving the first webhook generates the site's **secret** (32 characters), shown in the settings. With **Test** (blog) you get a `ping` event, and with **Send test alert** (alerts), an alert of type `Test`. ## How they arrive A `POST` with `Content-Type: application/json` and these headers: - `X-Jazui-Event`: the type (`Published`, `Updated`, `Unpublished`, `alert` or `ping`). - `X-Jazui-Delivery`: delivery ID, the same on retries (use it to avoid processing the same one twice). - `X-Jazui-Signature`: `sha256=` followed by the HMAC-SHA256 of the body with your secret, in lowercase hex. ```http 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=5d41402abc4b2a76b9719d911017c592ae8b6e1f0a2c4d3b5e7f9a1c3d5e7f90 ``` ## Blog events `event` is `article.published`, `article.updated` or `article.unpublished`. The same article with the same event isn't sent twice within 30 seconds. Use it to rebuild your static site, notify your team or publish elsewhere. ```json { "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" } } ``` ## Alerts `alert.type` is `RenderErrors` (render errors in 24 h), `GoogleDrop` (Googlebot visits dropping versus the previous week), `SeoDrop` (SEO score dropping over 7 days) or `Test`. `value` is what was measured and `threshold` the threshold you set. ```json { "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" } } ``` ## Test The blog webhook test button sends: ```json { "event": "ping", "site": "misitio.com", "occurredAt": "2026-11-03T09:00:00Z" } ``` ## Verifying the signature Compute the HMAC-SHA256 of the **body exactly as received** (without re-serializing it) with your secret and compare it with the header in constant time. If it doesn't match, answer `401` and don't process it. `Node.js (Express)`: ```javascript 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); }); ``` `C# (ASP.NET Core)`: ```csharp 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(); }); ``` ## Responses and retries - Any response below `500` counts as delivered: answer `2xx` as soon as you receive it and do the work afterwards. - With a `5xx` or no response, Jazui retries after 2 and 10 seconds (three attempts in total). - Each attempt waits at most 15 seconds and follows up to 3 redirects.