Outbound webhooks
Fire HMAC-signed POSTs to your URL when panel events happen — wire NovaPanel into Slack, Zapier, n8n, your CRM, billing, anything that speaks HTTP.
What it does
When something happens in the panel — a new account is provisioned, a site finishes deploying, a domain is added — NovaPanel fires an HTTP POST to a URL you configure. Each request is signed with HMAC-SHA256 so your receiver can verify it really came from your panel, not from a forged caller.
This is the integration spine: instead of writing a NovaPanel plugin for every external service you use, you point NovaPanel at the service's existing webhook endpoint (or a small adapter you control) and let events flow.
Available events
| Event | Fires when |
|---|---|
user.created | A new hosting account is provisioned. |
user.suspended | An account is suspended. |
user.unsuspended | A previously suspended account is reactivated. |
user.deleted | An account is fully removed. |
site.created | A site is created (any runtime). |
site.deployed | A site finishes a deployment — successful or failed. |
site.deleted | A site is removed. |
domain.added | A domain is attached to a site. |
domain.removed | A domain is detached from a site. |
ssl.issued | An SSL certificate is issued or renewed. |
php.installed | A PHP version finishes installing. |
php.install_failed | A PHP version install attempt fails. |
login.failed | A failed login attempt is recorded. |
backup.completed | A backup finishes. |
test.ping | You hit "Test" in the Webhooks page. |
Subscribe to a subset by checking specific events, or leave them all unchecked to receive every event the panel fires.
Step 1 — Create a webhook
Admin → Webhooks → New webhook. Give it a name (e.g. "Slack: deploys"), paste your receiver URL, pick the events you care about, and save. NovaPanel generates a 32-byte hex secret you'll use to verify signatures.
Copy the secret somewhere safe — you can reveal it again later from the webhook card, but it's most discoverable right after creation.
Step 2 — Test it
Hit the Test button on the webhook card. NovaPanel sends a synthetic test.ping event to your URL immediately, bypassing the active flag and event filter. Open the Log button to see the result — status code, response body, latency.
If you see a non-2xx status or a network error, fix that before relying on the webhook for real events. The Log panel auto-refreshes every 5 seconds.
Payload format
Every delivery is a JSON POST shaped like:
{
"event": "site.deployed",
"timestamp": "2026-05-12T14:32:01Z",
"data": {
"deployment_id": "8f3a…",
"site_id": "b21c…",
"site_name": "marketing-site",
"status": "success",
"user_id": "u_4f1a…"
}
}
The data shape varies by event — see the live event list at GET /api/v1/webhook-events on your admin panel for the canonical schema.
Headers sent
Content-Type: application/jsonUser-Agent: NovaPanel-Webhook/1.0X-NovaPanel-Event: <event name>— same value aseventin the bodyX-NovaPanel-Signature: sha256=<hex digest>— HMAC-SHA256 of the raw request body, keyed with the webhook's secret
Verifying the signature (Node.js)
const crypto = require('crypto');
function verifyNovaPanel(req, secret) {
const sig = (req.headers['x-novapanel-signature'] || '').replace(/^sha256=/, '');
const expected = crypto.createHmac('sha256', secret)
.update(req.rawBody) // must be the raw bytes, not the parsed JSON
.digest('hex');
if (sig.length !== expected.length) return false;
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Use express.raw() or equivalent middleware so req.rawBody is the unmodified bytes — re-serializing the parsed JSON will produce a different byte sequence and the HMAC will not match.
Verifying the signature (Python)
import hmac, hashlib
def verify_novapanel(raw_body: bytes, signature_header: str, secret: str) -> bool:
sig = signature_header.removeprefix("sha256=")
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(sig, expected) Delivery behaviour
- Async. Deliveries run in their own goroutine — slow receivers never block the panel.
- Timeout. 10 seconds per request. Treat receivers that need longer as a bug in the receiver.
- Retries. v1 doesn't auto-retry — failures are logged in the delivery panel so you can re-test or address the receiver. (Retry policy on the roadmap.)
- History. The last 200 deliveries per webhook are retained; older ones are pruned automatically.
- Failure visibility. Status codes ≥ 400 are flagged red in the Log; click the row to see the response body.
Pausing without deleting
Toggle Active off in the webhook editor. Events stop being delivered immediately, but the configuration, secret, and history are preserved so you can resume later without re-sharing the secret with your receiver.
Rotating the secret
Edit the webhook and paste a new secret in the Rotate secret field. The old secret is invalidated as soon as you save — make sure your receiver is updated first, or accept a brief gap while it propagates.
Common patterns
- Slack notifications — point at your channel's incoming-webhook URL and subscribe to
site.deployed+php.install_failedfor the things you actually want pinged about. - Zapier / n8n / Make — they all accept generic webhook triggers; subscribe to
user.createdto drive welcome emails or CRM lead creation. - Custom billing — subscribe to
user.created+user.deletedto keep your billing system's customer list in sync. - Audit forwarding — subscribe to everything and stream into your SIEM / log aggregator.