Webhooks
Get a signed POST when backups, restores and updates finish or fail, or when a site disconnects.
Updated Oct 11, 2026
Webhooks let WP Foreman tell your other tools when something happens. Point one at Zapier or Make to post into Slack, open a ticket, or text you when a backup fails.
Add a webhook
- In the app, open Settings › Webhooks.
- Paste the
https://URL that should receive events and pick the events. - Click Add webhook, then Send test to check it arrives.
Every webhook has a signing secret (starts with whsec_) so you can check deliveries really came from WP Foreman.
Events
| Event | When | data contains |
|---|---|---|
backup.succeeded |
A backup finished | site, backup |
backup.failed |
A backup failed | site, backup (with error) |
restore.succeeded |
A restore or clone finished | site, restore |
restore.failed |
A restore or clone failed | site, restore |
update.succeeded |
A plugin, theme or WordPress change finished | site, activity |
update.failed |
A change failed, or only some items worked | site, activity |
site.disconnected |
Someone clicked Disconnect in WordPress | site |
site.changed |
Someone changed a site outside WP Foreman: a plugin or theme installed, deleted, activated, deactivated or switched, WordPress updated by hand, or a new administrator (plugin 0.7.0+; auto-updates aren’t sent) | site, changes (each with type, title, by {name, email, login, roles}, context, ip, at) |
site.down |
Uptime monitoring confirmed a site is down | site, incident (reason, regions, started_at) |
site.up |
A site is back up after an outage | site, incident (with ended_at and seconds) |
visual.changed |
A visual check found pages that look different from their last screenshots | site, check (id, trigger: update, schedule or manual, activity_id, threshold), shots (each over the threshold: path, device, diff_pct) |
security.found |
Known vulnerabilities were found on a site (new ones only) | site, vulnerabilities (each: vulnerability_id, name, type, slug, installed_version, fixed_in, severity, title) |
performance.dropped |
A site’s mobile speed score fell 10+ points below its usual | site, strategy (mobile), score, usual, lcp_ms, checked_at |
rule.ran |
A rule with Send the rule.ran webhook ticked finished a run | rule {id, name, description}, trigger, status (done, problems, failed), sites (each with site_id, name, did, and skipped or error when there was one) |
* |
Everything, including events added later | |
Safety backups taken before restores and updates send backup events too, with trigger set to pre-restore or pre-update.
What a delivery looks like
POST /your/endpoint
Content-Type: application/json
User-Agent: WPForeman-Webhooks/1.0
X-WPF-Event: backup.failed
X-WPF-Delivery: 8f0e1c1a-2a9b-4b3c-9d61-0d3f6f1f7b9e
X-WPF-Signature: t=1791234567,v1=5d1c…e9
{
"id": "8f0e1c1a-2a9b-4b3c-9d61-0d3f6f1f7b9e",
"event": "backup.failed",
"created_at": "2026-10-05T05:41:12+00:00",
"data": {
"site": { "id": 12, "name": "Shop", "url": "https://shop.example", "client": { "id": 3, "name": "Acme" } },
"backup": { "id": 481, "status": "failed", "trigger": "scheduled", "error": "The site answered 503: Service Unavailable", … }
}
}
The backup, restore and activity objects are the same as in the API.
Verify the signature
X-WPF-Signature is t=<unix time>,v1=<hex HMAC-SHA256>, signed over <t>.<raw body> with your signing secret. Reject deliveries older than five minutes.
// PHP
[$t, $sig] = sscanf($_SERVER['HTTP_X_WPF_SIGNATURE'], 't=%d,v1=%s');
$body = file_get_contents('php://input');
$ok = abs(time() - $t) < 300
&& hash_equals(hash_hmac('sha256', $t . '.' . $body, $secret), $sig);
// Node.js (Express: use express.raw() so you get the exact body)
const crypto = require('crypto');
const [, t, sig] = req.get('X-WPF-Signature').match(/^t=(\d+),v1=([a-f0-9]+)$/);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${req.body}`).digest('hex');
const ok = Math.abs(Date.now() / 1000 - t) < 300 &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
Retries
- Answer with any
2xxwithin 15 seconds to confirm the delivery. - If your endpoint is down, times out, or answers
5xxor429, WP Foreman tries again after 1 minute, 5 minutes, 30 minutes and 2 hours. Other4xxanswers aren’t retried. - Use
id(also inX-WPF-Delivery) to ignore duplicates. - After 20 failed deliveries in a row the webhook is switched off. Fix it, then click Turn on.
- Recent deliveries on the Webhooks page shows what was sent and how your endpoint answered.
For safety, webhook URLs must use https:// and can’t point at private or local network addresses.
Subscribe over the API (Zapier, Make)
Tools that manage their own subscriptions can use a token with the webhooks ability:
GET /webhooks
POST /webhooks
DELETE /webhooks/{id}
curl -X POST https://app.wpforeman.com/api/v1/webhooks \
-H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" \
-d '{"url":"https://hooks.zapier.com/…","events":["backup.failed","update.failed"]}'
The response includes the secret. It’s only returned when the webhook is created.
Slack: Slack’s own “incoming webhook” URLs expect a different format. Send WP Foreman’s webhook to Zapier or Make and have it post a message to Slack, e.g. “⚠️ Backup failed on {{site.name}}: {{backup.error}}”.
Thanks. If something was missing, tell us what.