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
- An event occurs in the panel (e.g. website created)
- The panel finds all active webhook endpoints subscribed to this event
- For each endpoint, an HTTP POST with the JSON payload is sent
- The payload is signed with HMAC-SHA256
- 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¶
- Navigate to Settings → Webhooks
- Click Create Webhook
- Fill in URL, secret and events
- 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:
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¶
Verification Steps¶
- Read the raw request body (as a byte array)
- Compute HMAC-SHA256 using your secret as the key
- Compare the result with the value from
X-Webhook-Signature - 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¶
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¶
- Navigate to Settings → Webhooks
- Click on a webhook endpoint
- 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:
This sends a webhook.test event with test data to the configured URL.
Or in the panel:
- Open the webhook endpoint
- Click Send Test
- 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¶
Disable Endpoint¶
Set active to false to temporarily disable an endpoint without deleting it:
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 |