Webhooks
Receive real-time notifications when audits and autofix runs complete — no polling required.
Events
Subscribe to one or more of these event types when registering a webhook endpoint.
| Event | Description |
|---|---|
audit.completed | Fired when an audit finishes successfully. Includes the full result summary. |
audit.failed | Fired when an audit fails due to an error (e.g. invalid ZIP, API timeout). |
autofix.completed | Fired when an AI autofix run finishes and a patch is available for download. |
autofix.failed | Fired when an autofix run could not produce a valid patch. |
Registration
Register a webhook endpoint using the API. Your URL must use HTTPS and return a 2xx status within 10 seconds.
curl -X POST https://wphealthkit.com/api/v1/webhooks \
-H "Authorization: Bearer whk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/wphk",
"events": ["audit.completed", "audit.failed"],
"secret": "your_signing_secret"
}'Response:
{
"data": {
"id": "wh_01j9x4r2v8kz3m7p6nq5w0c1d",
"url": "https://yourapp.com/webhooks/wphk",
"events": ["audit.completed", "audit.failed"],
"created_at": "2025-04-10T12:00:00.000Z"
},
"error": null,
"meta": { "timestamp": "2025-04-10T12:00:00.000Z" }
}Payload Format
Payloads are sent as JSON via HTTP POST. Each payload includes the event type, a unique delivery ID, and the event-specific data.
audit.completed
{
"id": "evt_01j9x4r2v8kz3m7p6nq5w0c1d",
"event": "audit.completed",
"created_at": "2025-04-10T12:01:14.000Z",
"data": {
"audit_id": "aud_01j9x4r2v8kz3m7p6nq5w0c1d",
"slug": "contact-form-7",
"plugin_name": "Contact Form 7",
"plugin_version": "5.9.8",
"overall_risk": "LOW",
"findings_count": 3,
"critical_count": 0,
"high_count": 0,
"report_url": "https://wphealthkit.com/results/aud_01j9x4r2v8kz3m7p6nq5w0c1d"
}
}audit.failed
{
"id": "evt_02k0y5s3w9la4n8q7or6x1d2e",
"event": "audit.failed",
"created_at": "2025-04-10T12:00:58.000Z",
"data": {
"audit_id": "aud_02k0y5s3w9la4n8q7or6x1d2e",
"slug": "broken-plugin",
"error": "Could not extract ZIP — invalid archive"
}
}Signature Verification
When you register a webhook with a secret, WP HealthKit signs every delivery using HMAC-SHA256. Verify the signature to ensure the request originated from us and has not been tampered with.
Always verify signatures
Skipping signature verification allows anyone who knows your endpoint URL to send forged payloads.
The signature is sent in the X-WPHealthKit-Signature header as sha256=<hex>.
Node.js (Express)
import crypto from "crypto";
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express handler
app.post("/webhooks/wphk", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-wphealthkit-signature"] as string;
const isValid = verifyWebhookSignature(req.body.toString(), sig, process.env.WPHK_WEBHOOK_SECRET!);
if (!isValid) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
console.log("Received event:", event.event);
res.json({ received: true });
});PHP (WordPress)
add_action('rest_api_init', function() {
register_rest_route('my-plugin/v1', '/wphk-webhook', [
'methods' => 'POST',
'callback' => 'handle_wphk_webhook',
'permission_callback' => '__return_true',
]);
});
function handle_wphk_webhook(WP_REST_Request $request) {
$body = $request->get_body();
$sig = $request->get_header('x-wphealthkit-signature');
$secret = defined('WPHK_WEBHOOK_SECRET') ? WPHK_WEBHOOK_SECRET : '';
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
if (!hash_equals($expected, $sig ?? '')) {
return new WP_Error('invalid_signature', 'Signature mismatch', ['status' => 401]);
}
$event = json_decode($body, true);
// Handle event...
return ['received' => true];
}Retry Policy
If your endpoint returns a non-2xx status code or does not respond within 10 seconds, WP HealthKit will retry delivery up to 3 times using exponential backoff.
| Attempt | Delay after failure |
|---|---|
| 1 (initial) | Immediate |
| 2 | 5 minutes |
| 3 | 30 minutes |
After 3 failed attempts the delivery is marked as abandoned. Delivery IDs are unique — use the id field to deduplicate events on your side in case retries succeed.
Register your first webhook
Use the API or the dashboard to manage webhook endpoints.