Webhooks
Ten events, one JSON body, an HMAC signature to check. Enough to post to Slack, open a ticket or update your own dashboard.
Set up
Settings → API and MCP → Webhooks → Add endpoint: an https:// address and the events you want. You get a secret, shown once, for checking signatures.
- Edit changes the address and the events. The secret stays the same.
- Test sends a
pingevent right away, so you can check your receiver. - Log shows the last deliveries and how many tries each took.
Events
| Event | When |
|---|---|
issue.resolved |
a recheck or audit no longer finds an issue |
issue.reopened |
a resolved issue came back |
audit.finished |
a full audit finished (pages, open issues, scores) |
score.changed |
the website score moved by 2 points or more after an audit |
connection.broken |
a Google or WordPress connection stopped working |
recheck.failed |
a recheck ran and the issue is still there, a written fix is not on the live page yet, or the recheck itself could not run |
write.applied |
an approved change (SEO field, llms.txt, AI crawler rules) was written through WordPress or the Shopify app |
write.failed |
an approved change could not be written |
write.not_live |
a recheck did not find a written value on the live page |
agentic.finished |
an Agentic browsing re-run finished or failed |
Delivery
POST https://your-endpoint
Content-Type: application/json
User-Agent: MonoRanks-Webhooks/1
X-MonoRanks-Event: recheck.failed
X-MonoRanks-Delivery: <delivery id>
X-MonoRanks-Signature: sha256=<hex>
Every body has id (the delivery id), event, at, workspaceId and siteId; most also have host and a link to the screen in the app.
{
"id": "…",
"event": "recheck.failed",
"at": "2026-10-01T09:12:00.000Z",
"workspaceId": "…",
"siteId": "…",
"issue": { "id": "…", "ruleId": "SEO-ONP-011", "title": "Missing meta description", "severity": "serious", "pages": 3 },
"crawlId": "…",
"reason": "still_present",
"recheck": { "present": 3, "checked": 4 },
"link": "https://app.monoranks.com/sites/…/issues/…"
}
What each event adds:
issue.resolved,issue.reopened:issue(id,ruleId,title,severity,pages) andcrawlId.audit.finished:crawlId,status,pagesCrawled,openIssuesandscore(site,health,aeo).score.changed:from,to,deltaandsince.connection.broken:connection(for examplegscorwordpress) anderror.recheck.failed:issue,reason(still_present,not_liveorerror) andrecheck(present,checked).write.applied,write.failed,write.not_live: one event per approved batch, withbatchId,issueIds,source(wordpressorshopify) and up to 50changes, each withurl,field,value,statusanderror. Forwrite.not_live, each change also hasnotLiveReasonandliveValue(what the page shows).agentic.finished:jobId,url,strategy,status(doneorfailed),passedofapplicable, thefailingchecks anderror.
Answer with any 2xx within 20 seconds. Anything else is tried again.
Checking the signature
The signature is an HMAC-SHA256 of the raw request body with your endpoint secret:
import { createHmac, timingSafeEqual } from 'node:crypto';
const expected = 'sha256=' + createHmac('sha256', SECRET).update(rawBody).digest('hex');
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-monoranks-signature']));
Use the raw body as received, not a re-serialized copy.
Common questions
What if my endpoint is down?
A failed delivery is tried again after 1, 5, 15 and 60 minutes, five tries in all. After 20 failures in a row the endpoint is switched off. Each delivery has an id in the X-MonoRanks-Delivery header so you can drop duplicates.
Can I filter by website?
The body carries the website id (siteId) and usually its host; filter on your side. Endpoints are per workspace.
I added an endpoint before the newer events existed. Do I get them?
No. An endpoint keeps the events it has ticked. Edit it to add the new ones.