Webhooks
Webhooks deliver real-time event notifications to your application via HTTP POST requests. Each delivery is HMAC-signed for verification.
Setup
- Navigate to Settings → Webhooks or visit your webhook endpoints
- Click New Webhook Endpoint
- Enter your endpoint URL (must be HTTPS in production)
- Select the events you want to subscribe to
- Save — a signing secret will be generated automatically
Events
| Event | Description |
|---|---|
site.down |
A monitored site is unreachable or returning errors |
site.recovered |
A previously down site is back online |
site.connected |
A new site has been connected and verified |
site.disconnected |
A site's connection has been lost |
security.vulnerability_found |
A new vulnerability has been detected |
security.scan_completed |
A security scan has finished |
performance.degraded |
Site performance has dropped below thresholds |
performance.check_completed |
A performance check has finished |
dns.changed |
DNS records for a site have changed |
upgrade.started |
An upgrade pipeline has started running |
upgrade.completed |
An upgrade pipeline has completed successfully |
upgrade.failed |
An upgrade pipeline has failed |
upgrade.rolled_back |
An upgrade has been rolled back |
snapshot.created |
A new site snapshot has been created |
snapshot.expired |
A snapshot has expired and been cleaned up |
Payload Format
All webhook deliveries are JSON POST requests with the following structure:
{
"event": "site.down",
"timestamp": "2026-03-17T12:00:00Z",
"site": {
"id": "site_abc123",
"name": "My Site",
"url": "https://example.com"
},
"data": {
// Event-specific data
}
}
Signature Verification
Every webhook includes an X-WPSentinel-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this signature before processing the payload.
Ruby
require "openssl"
def verify_webhook(payload_body, signature_header, secret)
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, payload_body)
Rack::Utils.secure_compare(expected, signature_header)
end
# In your controller:
payload = request.body.read
signature = request.headers["X-WPSentinel-Signature"]
secret = ENV["WPSENTINEL_WEBHOOK_SECRET"]
unless verify_webhook(payload, signature, secret)
head :unauthorized
return
end
event = JSON.parse(payload)
Python
import hmac
import hashlib
import json
def verify_webhook(payload_body, signature, secret):
expected = hmac.new(
secret.encode(),
payload_body.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# In your Flask/Django handler:
signature = request.headers.get("X-WPSentinel-Signature")
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
abort(401)
JavaScript (Node.js)
const crypto = require("crypto");
function verifyWebhook(payloadBody, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payloadBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
// In your Express handler:
const signature = req.headers["x-wpsentinel-signature"];
if (!verifyWebhook(req.rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
Retry Policy
If your endpoint returns a non-2xx status code, WPSentinel will retry the delivery. Failed deliveries can be viewed in the webhook endpoint's delivery history.
Best Practices
- Always verify signatures before processing webhooks
- Return 200 quickly and process the event asynchronously
- Handle duplicate deliveries — use the event timestamp for idempotency
- Use HTTPS endpoints to protect the payload in transit