---
title: "Webhooks"
url: https://wpforeman.com/docs/api-webhooks/
description: "Get a signed POST when backups, restores and updates finish or fail, or when a site disconnects."
updated: 2026-10-31
---

# Webhooks

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

1. In the app, open **Settings › Webhooks**.
2. Paste the `https://` URL that should receive events and pick the events.
3. 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`) |
| `work.logged` | Someone logged hands-on work on a site (in the app, the API or the Foreman) | `site`, `work` (`id`, `title`, `description`, `performed_on`, `minutes`, `time`, `show_on_report`, `by`) |
| `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](https://wpforeman.com/docs/how-rules-work/) 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 `2xx` within 15 seconds to confirm the delivery.
- If your endpoint is down, times out, or answers `5xx` or `429`, WP Foreman tries again after 1 minute, 5 minutes, 30 minutes and 2 hours. Other `4xx` answers aren’t retried.
- Use `id` (also in `X-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}}”.

---

WP Foreman: WordPress site management for agencies and freelancers. First site free, no card: https://app.wpforeman.com/signup
