Skip to content

Webhooks

Webhooks allow you to automatically notify external systems about events in enconf. For each configured event, the panel sends an HTTP POST request with the event data to your URL.

Typical use cases:

  • Automatic deployment pipelines after website creation
  • Notifications to chat systems (Slack, Microsoft Teams, Discord)
  • Synchronization with billing systems
  • Monitoring and logging to external platforms

How It Works

Event in Panel → Webhook Service → HTTP POST to your URL
                        Signature (HMAC-SHA256) in header
                        Your server verifies and processes
  1. An event occurs in the panel (e.g. website created)
  2. The panel finds all active webhook endpoints subscribed to this event
  3. For each endpoint, an HTTP POST with the JSON payload is sent
  4. The payload is signed with HMAC-SHA256
  5. On failure, an automatic retry is performed

Create Webhook Endpoint

Via the API

POST /api/v1/webhooks
Authorization: Bearer <token>
Content-Type: application/json

{
  "url": "https://your-server.com/webhook",
  "secret": "YourSecretWebhookSecret",
  "events": "site.created,site.deleted,backup.completed",
  "active": true
}
Field Required Description
url Yes The URL where events are sent (must be HTTPS)
secret Yes Secret key for the HMAC-SHA256 signature
events Yes Comma-separated list of desired events (or * for all)
active No Enable/disable the endpoint (default: true)

Via the Panel

  1. Navigate to SettingsWebhooks
  2. Click Create Webhook
  3. Fill in URL, secret and events
  4. Click Create

Available Events

Event Description Payload Data
site.created Website was created site_id, domain
site.deleted Website was deleted site_id, domain
domain.created Domain was added domain_id, name
domain.deleted Domain was removed domain_id, name
database.created Database was created database_id, name
database.deleted Database was deleted database_id, name
email.created Email mailbox was created mailbox_id, email
ftp.created FTP account was created ftp_account_id, username
backup.completed Backup was completed schedule_id, name
subscription.created Subscription was created subscription_id, main_domain
subscription.suspended Subscription was suspended subscription_id, main_domain
webhook.test Test event (manually triggered) message

Wildcard Subscription

Use * as the event to receive all events:

{
  "events": "*"
}

Payload Format

Each webhook call sends a JSON body with the following format:

{
  "event": "site.created",
  "timestamp": "2026-03-31T14:30:00Z",
  "data": {
    "site_id": 42,
    "domain": "my-site.com"
  }
}
Field Type Description
event String Name of the event
timestamp String Time of the event (RFC 3339, UTC)
data Object Event-specific data

HTTP Headers

Each webhook request contains the following headers:

Header Description
Content-Type application/json
User-Agent NetCell-WebPanel-Webhook/1.0
X-Webhook-Event Name of the event (e.g. site.created)
X-Webhook-Signature HMAC-SHA256 signature (see below)

HMAC Signature Verification

Each webhook request is signed with HMAC-SHA256 so you can verify the authenticity and integrity of the payload.

Signature Format

X-Webhook-Signature: sha256=<hex-encoded-hmac>

Verification Steps

  1. Read the raw request body (as a byte array)
  2. Compute HMAC-SHA256 using your secret as the key
  3. Compare the result with the value from X-Webhook-Signature
  4. Accept the request only if they match

Example: PHP

<?php
$secret = 'YourSecretWebhookSecret';
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';

// Compute expected signature
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);

// Use timing-safe comparison function
if (!hash_equals($expected, $signature)) {
    http_response_code(403);
    die('Invalid signature');
}

// Process payload
$data = json_decode($payload, true);
$event = $data['event'];

switch ($event) {
    case 'site.created':
        // Website was created
        $siteId = $data['data']['site_id'];
        $domain = $data['data']['domain'];
        // Your logic here...
        break;

    case 'backup.completed':
        // Backup completed
        break;
}

http_response_code(200);
echo json_encode(['status' => 'ok']);

Example: Python

import hmac
import hashlib
import json
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = 'YourSecretWebhookSecret'

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    # Verify signature
    signature = request.headers.get('X-Webhook-Signature', '')
    expected = 'sha256=' + hmac.new(
        SECRET.encode(),
        request.data,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        abort(403, 'Invalid signature')

    # Process payload
    data = request.json
    event = data['event']

    if event == 'site.created':
        site_id = data['data']['site_id']
        domain = data['data']['domain']
        # Your logic here...

    return {'status': 'ok'}, 200

Example: Node.js

const crypto = require('crypto');
const express = require('express');
const app = express();

const SECRET = 'YourSecretWebhookSecret';

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'] || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', SECRET)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  )) {
    return res.status(403).json({ error: 'Invalid signature' });
  }

  const data = JSON.parse(req.body);
  console.log(`Event: ${data.event}`, data.data);

  res.json({ status: 'ok' });
});

app.listen(3000);

Always Verify Signature

Never process webhook payloads without signature verification. Without verification, an attacker could send forged events to your URL.

Timing-Safe Comparisons

Always use timing-safe comparison functions (hash_equals in PHP, hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js) to prevent timing attacks.


Retry Behavior

If your server does not respond with an HTTP status code in the 2xx range, the panel automatically performs a retry:

Attempt Timing
1st Attempt Immediately
2nd Attempt (Retry) 2 seconds after the first failure

After two failed attempts, the delivery is marked as failed and logged in the delivery log.

Timeout

Your server must respond within 10 seconds. Requests that take longer are considered failed.

Recommendations for Your Webhook Server

  • Respond immediately with HTTP 200 and process the payload asynchronously
  • Implement idempotency — the same event may be delivered twice
  • Log incoming webhooks for debugging purposes

Delivery Logs

The panel stores the last 100 delivery attempts per webhook endpoint.

Retrieve Logs

GET /api/v1/webhooks/:id/deliveries
Authorization: Bearer <token>

Log Entries

Field Description
id Unique ID of the delivery attempt
event Triggered event
payload The sent JSON payload
status_code HTTP status code of the response (0 for connection error)
response Response body from your server (max. 4 KB)
success Whether the delivery was successful
duration_ms Duration of the request in milliseconds
created_at Time of the delivery attempt

View Logs in the Panel

  1. Navigate to SettingsWebhooks
  2. Click on a webhook endpoint
  3. In the Deliveries tab you can see the history of all delivery attempts

Send Test Webhook

To test your integration, you can manually trigger a test event:

POST /api/v1/webhooks/:id/test
Authorization: Bearer <token>

This sends a webhook.test event with test data to the configured URL.

Or in the panel:

  1. Open the webhook endpoint
  2. Click Send Test
  3. Check the delivery in the delivery log

Manage Webhooks

Update Endpoint

PUT /api/v1/webhooks/:id
Authorization: Bearer <token>
Content-Type: application/json

{
  "url": "https://new-url.com/webhook",
  "events": "site.created,domain.created",
  "active": true
}

Delete Endpoint

DELETE /api/v1/webhooks/:id
Authorization: Bearer <token>

Disable Endpoint

Set active to false to temporarily disable an endpoint without deleting it:

{
  "active": false
}

Best Practices

Recommendation Description
Use HTTPS Always use HTTPS for your webhook URL
Verify Signature Verify every request with the HMAC secret
Respond Quickly Respond within 2 seconds, process asynchronously
Idempotency Expect that events may be delivered twice
Fault Tolerance Log and monitor failed deliveries
Rotate Secret Change your webhook secret regularly
IP Whitelist Restrict access to your webhook URL to the panel IP