Automations & webhooks
Automations
Automations let nuzur call your systems when your data changes — for example, when a record's status becomes PUBLISHED, nuzur can POST a signed webhook to your API so it can react (send a notification, kick off a job, sync another service).
The key property: automations only fire on approved changes. Every data modification in nuzur goes through a change request, and an automation runs only when a change request is approved and applied. Nothing — not a teammate, not an AI agent — can trigger your webhook without that change having gone through review. Your receiving system only ever sees changes a human signed off on.
Create an automation
Open the Data Manager for your project and switch to the Automations tab in the left panel (next to Entities), then click New automation.
An automation has:
- Name — a human label, e.g.
Episode published → scoring. - Entity — the table to watch.
- Operation — fire on
create,update,delete, orany. - Conditions (optional) — one or more "when field X changes to Y" rules, combined with AND (all must hold in the same change; default) or OR (any one is enough). The single-field transition is the most common pattern for lifecycle-driven automations: a record is created as a draft and later updated to
PUBLISHED. For enum fields the "changes to" input offers the enum's options directly. A condition only fires on a real transition — an update that leaves the field at a value it already had doesn't count. If you need anything fancier than AND/OR field transitions, leave the conditions empty and filter on the receiving side — your endpoint may receive events it doesn't care about; check the payload. - Webhook URL — where nuzur POSTs the event. Must be
httpsand publicly reachable (private and local addresses are rejected).
When you create the automation, nuzur shows you the signing secret exactly once. Copy it and store it in your receiving service's configuration — it is used to verify that deliveries really come from nuzur, and it cannot be retrieved later.
If the secret is lost or may have leaked, open the automation and click Rotate secret (or use the rotateAutomationSecret MCP tool). A new secret is generated and shown once; the old one stops signing new deliveries within about a minute. There is no dual-secret overlap, so update your receiver's configuration immediately after rotating — deliveries that fail during the switch retry automatically with the new signature.
You can also manage automations from your AI tools through the nuzur MCP server — the createAutomation, testAutomation, and listAutomationEvents tools mirror everything described here.
What your endpoint receives
nuzur POSTs a JSON body shaped like this:
{
"event_uuid": "5f0c2f6e-6a41-4e6e-9b1a-2f8f6f2f7c11",
"sent_at": "2026-07-13T18:04:05Z",
"automation": { "uuid": "…", "name": "Episode published → scoring" },
"project_uuid": "…",
"entity": { "uuid": "…", "identifier": "episode" },
"operation": "update",
"record": {
"keys": { "uuid": "d3b1…" },
"changes": {
"status": { "old": "DRAFT", "new": "PUBLISHED" }
}
},
"change_request": {
"uuid": "…",
"title": "Publish episode 12",
"approved_by_uuid": "…",
"applied_at": "2026-07-13T18:04:01Z"
}
}
record.keysidentifies the row;record.changescarries old/new values for updates; creates carryrecord.valuesinstead; deletes carry onlykeys.change_requesttells you who approved it and when — provenance no database-level CDC tool can give you.- Test deliveries (from the Send test delivery button) additionally carry
"test": true.
Delivery rules:
- Any 2xx response counts as success. Respond quickly and do your work asynchronously — the request times out after 10 seconds.
- Delivery is at-least-once and unordered. Duplicates are possible; deduplicate on
event_uuid. - Failed deliveries retry with backoff (1m, 5m, 30m, 2h, 12h) and are then marked dead. You can inspect and retry them from the Deliveries log on each automation.
- Redirects are not followed.
Verify the signature
Every delivery includes two headers:
X-Nuzur-Event-Id: 5f0c2f6e-6a41-4e6e-9b1a-2f8f6f2f7c11
X-Nuzur-Signature: sha256=8f2c1a…
The signature is the hex HMAC-SHA256 of the raw request body, computed with your signing secret. Verify it before trusting the payload:
- Read the raw bytes of the request body — before any JSON parsing or middleware touches it. Re-serialized JSON will not match.
- Compute
HMAC-SHA256(raw_body, secret)and hex-encode it. - Compare against the header value (after the
sha256=prefix) using a constant-time comparison. - Reject anything that doesn't match, then deduplicate on
event_uuid. Optionally reject events whosesent_atis older than a few minutes to narrow the replay window.
Go
func verifyNuzurSignature(rawBody []byte, header, secret string) bool {
sig, ok := strings.CutPrefix(header, "sha256=")
if !ok {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(sig))
}
func handler(w http.ResponseWriter, r *http.Request) {
rawBody, _ := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
if !verifyNuzurSignature(rawBody, r.Header.Get("X-Nuzur-Signature"), os.Getenv("NUZUR_SIGNING_SECRET")) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
// parse rawBody, dedupe on event_uuid, then do your work async
w.WriteHeader(http.StatusOK)
}
Node
import crypto from "node:crypto";
import express from "express";
const app = express();
// capture the RAW body — express.json() alone re-parses it and the
// signature will not match
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
function verifyNuzurSignature(rawBody, header, secret) {
if (!header?.startsWith("sha256=")) return false;
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const received = header.slice("sha256=".length);
return (
expected.length === received.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
);
}
app.post("/hooks/nuzur", (req, res) => {
if (!verifyNuzurSignature(req.rawBody, req.get("X-Nuzur-Signature"), process.env.NUZUR_SIGNING_SECRET)) {
return res.status(401).send("invalid signature");
}
// dedupe on req.body.event_uuid, then do your work async
res.sendStatus(200);
});
Test before you ship
Use the Send test delivery button (or the testAutomation MCP tool) to POST a synthetic, fully signed event to your endpoint — the payload has the exact shape of a real delivery with sample values and "test": true. You can build and verify your receiver end to end before any real change request exists.
Monitoring deliveries
Each automation has a Deliveries log: every event with its status (pending, delivering, delivered, failed, dead), attempt count, last error, and the frozen payload. Failed and dead events can be retried manually.
Two things to know:
- If an endpoint keeps failing, the automation is flagged needs attention after several consecutive dead events and stops firing — the configuration is kept, and a banner appears in the Automations tab. Fix the endpoint and save the automation to reactivate it.
- If a schema change removes the entity or the condition field an automation watches, it is also flagged needs attention at publish time instead of firing incorrectly.