NovaPanel
Docs

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

EventFires when
user.createdA new hosting account is provisioned.
user.suspendedAn account is suspended.
user.unsuspendedA previously suspended account is reactivated.
user.deletedAn account is fully removed.
site.createdA site is created (any runtime).
site.deployedA site finishes a deployment — successful or failed.
site.deletedA site is removed.
domain.addedA domain is attached to a site.
domain.removedA domain is detached from a site.
ssl.issuedAn SSL certificate is issued or renewed.
php.installedA PHP version finishes installing.
php.install_failedA PHP version install attempt fails.
login.failedA failed login attempt is recorded.
backup.completedA backup finishes.
test.pingYou 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/json
  • User-Agent: NovaPanel-Webhook/1.0
  • X-NovaPanel-Event: <event name> — same value as event in the body
  • X-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_failed for the things you actually want pinged about.
  • Zapier / n8n / Make — they all accept generic webhook triggers; subscribe to user.created to drive welcome emails or CRM lead creation.
  • Custom billing — subscribe to user.created + user.deleted to keep your billing system's customer list in sync.
  • Audit forwarding — subscribe to everything and stream into your SIEM / log aggregator.