This guide covers everything about Panelica's webhook system --- creating webhooks, subscribing to events, configuring destinations (HTTP, Telegram, Slack, Discord), verifying signatures, monitoring deliveries, and handling retries.
Webhooks are automated HTTP callbacks that notify your application when events happen on your Panelica server. Instead of constantly polling the API to check for changes, webhooks push notifications to your endpoint in real-time.
Example: When a new domain is created, Panelica instantly sends a POST request to your configured URL with the domain details.
Supported Destinations:
- HTTP/HTTPS --- Send JSON payloads to any URL
- Telegram --- Receive notifications in a Telegram chat or group
- Slack --- Post messages to a Slack channel
- Discord --- Send embeds to a Discord channel
---
Go to Developer > Webhooks in the panel navigation.
The page has 4 sections:
| Section | Description |
|---|---|
| Webhooks List | All your configured webhooks with status and stats |
| Delivery Logs | Historical delivery attempts with request/response details |
| Event Categories | Browse all 160+ available events organized by category |
| Statistics | Success rate, average response time, delivery metrics |
---
Step 1: Click Create Webhook.
Step 2: Fill in the general settings:
| Field | Required | Description |
|---|---|---|
| Name | Yes | A descriptive name (e.g., "Slack Domain Alerts", "Billing Webhook") |
| Description | No | Optional notes about this webhook's purpose |
| Active | No | Enable/disable the webhook (default: active) |
Step 3: Configure the destination (see destination-specific sections below).
Step 4: Select events to subscribe to (see Event Categories section).
Step 5: Configure retry settings:
| Field | Default | Description |
|---|---|---|
| Retry Count | 3 | Maximum number of retry attempts on failure |
| Retry Delay | 60 seconds | Base delay between retries (exponential backoff applied) |
| Timeout | 30 seconds | Maximum time to wait for a response |
Step 6: Click Create.
Step 7: Copy the webhook secret! A secret is generated:
Code:
whsec_a1b2c3d4e5f6789...
---
Send JSON payloads to any HTTP endpoint.
Configuration:
| Field | Required | Description |
|---|---|---|
| URL | Yes | The endpoint URL (must be HTTPS for production) |
| Method | No | HTTP method: POST (default), PUT, or PATCH |
| Custom Headers | No | Additional headers to include in each request |
| Timeout | No | Request timeout in seconds (default: 30) |
Headers Sent with Each Request:
Code:
Content-Type: application/json
X-Panelica-Signature: sha256=a1b2c3d4e5f6...
X-Panelica-Event: domain.created
X-Panelica-Delivery: 550e8400-e29b-41d4-a716-446655440000
User-Agent: Panelica-Webhook/1.0
Payload Format:
Code:
{
"event_type": "domain.created",
"event_id": "evt_550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-03-07T12:00:00Z",
"webhook_id": "uuid-of-webhook",
"data": {
"id": "domain-uuid",
"domain": "example.com",
"php_version": "8.4",
"status": "active",
"created_by": "admin"
},
"_meta": {
"user_id": "user-uuid",
"source_ip": "192.168.1.100"
}
}
SSRF Protection: Panelica validates webhook URLs to prevent Server-Side Request Forgery:
- Localhost and loopback addresses are blocked (
127.0.0.1,::1,0.0.0.0) - Private network ranges are blocked (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16) - Link-local, multicast, and reserved ranges are blocked
- DNS resolution is validated (hostname must resolve to a public IP)
---
When you receive a webhook, verify the
X-Panelica-Signature header to ensure it's genuinely from Panelica.How it works:
- Take the raw request body (JSON string)
- Compute HMAC-SHA256 using your webhook secret
- Compare with the
X-Panelica-Signatureheader value (after removing thesha256=prefix)
Python Example:
Code:
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret_here"
@app.route('/webhook', methods=['POST'])
def handle_webhook():
# Get the signature from headers
signature = request.headers.get('X-Panelica-Signature', '')
if signature.startswith('sha256='):
signature = signature[7:]
# Compute expected signature
body = request.get_data()
expected = hmac.new(
WEBHOOK_SECRET.encode(),
body,
hashlib.sha256
).hexdigest()
# Constant-time comparison
if not hmac.compare_digest(signature, expected):
abort(401, 'Invalid signature')
# Process the event
payload = request.get_json()
event_type = payload['event_type']
if event_type == 'domain.created':
domain = payload['data']['domain']
print(f"New domain created: {domain}")
elif event_type == 'backup.failed':
print(f"Backup failed! Details: {payload['data']}")
return '', 200
Node.js Example:
Code:
const crypto = require('crypto');
const express = require('express');
const app = express();
const WEBHOOK_SECRET = 'whsec_your_secret_here';
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-panelica-signature'] || '';
const sig = signature.replace('sha256=', '');
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body);
console.log(`Event: ${payload.event_type}`, payload.data);
res.status(200).send('OK');
});
app.listen(3000);
PHP Example:
Code:
<?php
$secret = 'whsec_your_secret_here';
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PANELICA_SIGNATURE'] ?? '';
// Remove 'sha256=' prefix
$sig = str_replace('sha256=', '', $signature);
// Compute expected signature
$expected = hash_hmac('sha256', $body, $secret);
// Constant-time comparison
if (!hash_equals($expected, $sig)) {
http_response_code(401);
die('Invalid signature');
}
$payload = json_decode($body, true);
$eventType = $payload['event_type'];
switch ($eventType) {
case 'domain.created':
// Handle new domain
break;
case 'ssl.expiring_soon':
// Send alert
break;
}
http_response_code(200);
echo 'OK';
---
Receive webhook notifications as Telegram messages.
Prerequisites:
- Create a Telegram bot via
@BotFatherand get the bot token - Add the bot to your group/channel or start a direct chat
- Get the chat ID (use
@userinfobotor the Telegram API)
Configuration:
| Field | Required | Description |
|---|---|---|
| Bot Token | Yes | Token from @BotFather (e.g., 123456:ABC-DEF...) |
| Chat ID | Yes | Target chat/group/channel ID |
| Parse Mode | No | HTML (default), Markdown, or MarkdownV2 |
| Disable Notifications | No | Send silently without notification sound |
Message Format:
Panelica formats events as readable Telegram messages with HTML formatting:
Code:
Panelica Webhook: domain.created
Event Details:
{
"domain": "example.com",
"php_version": "8.4",
"status": "active"
}
---
Post webhook notifications to a Slack channel.
Prerequisites:
- Go to your Slack workspace settings
- Create an Incoming Webhook (Apps > Incoming Webhooks)
- Select the target channel and copy the webhook URL
Configuration:
| Field | Required | Description |
|---|---|---|
| Webhook URL | Yes | Slack incoming webhook URL |
| Channel | No | Override the default channel (e.g., #alerts) |
| Username | No | Override the bot username |
| Icon Emoji | No | Override the bot icon (e.g., :bell:) |
Message Format:
Slack messages use Markdown formatting with code blocks:
Code:
* Panelica Webhook: domain.created*
```json
{
"domain": "example.com",
"php_version": "8.4"
}
```
---
Send webhook notifications as Discord embeds.
Prerequisites:
- In your Discord server, go to Channel Settings > Integrations > Webhooks
- Create a webhook and copy the URL
Configuration:
| Field | Required | Description |
|---|---|---|
| Webhook URL | Yes | Discord webhook URL |
| Username | No | Override the bot username |
| Avatar URL | No | Override the bot avatar image |
Embed Colors:
Discord embeds are color-coded by event type:
- Green --- Success events (created, started, issued, renewed)
- Blue --- Update events (updated, changed, synced)
- Red --- Delete/failure events (deleted, failed, expired)
- Yellow --- Warning events (warning, expiring_soon, brute_force)
- Gray --- Other events
---
Panelica supports 160+ webhook events across 27 categories. Here are the most commonly used:
User Events
| Event | Triggered When |
|---|---|
user.created | A new user account is created |
user.updated | User profile is updated |
user.deleted | User account is deleted |
user.suspended | User account is suspended |
user.unsuspended | User account is unsuspended |
user.login | Successful login |
user.login_failed | Failed login attempt |
user.password_changed | Password changed |
user.2fa_enabled | Two-factor authentication enabled |
user.2fa_disabled | Two-factor authentication disabled |
user.role_changed | User role changed (e.g., USER to RESELLER) |
Domain Events
| Event | Triggered When |
|---|---|
domain.created | New domain added |
domain.updated | Domain settings changed |
domain.deleted | Domain removed |
domain.suspended | Domain suspended |
domain.unsuspended | Domain activated |
domain.php_updated | PHP version changed |
domain.webserver_changed | Web server switched (Nginx/Apache) |
SSL Events
| Event | Triggered When |
|---|---|
ssl.issued | New SSL certificate issued |
ssl.renewed | Certificate renewed |
ssl.expired | Certificate expired |
ssl.expiring_soon | Certificate expiring within 14 days |
ssl.auto_renew_failed | Auto-renewal failed |
Security Events
| Event | Triggered When |
|---|---|
security.ip_blocked | IP address blocked (manual or Fail2ban) |
security.brute_force_detected | Brute force attack detected |
security.modsec_triggered | ModSecurity WAF rule triggered |
security.malware_detected | ClamAV found malware |
security.ssh_login | SSH login detected |
Backup Events
| Event | Triggered When |
|---|---|
backup.created | Backup completed successfully |
backup.failed | Backup failed |
backup.restored | Backup restored |
backup.uploaded_remote | Backup uploaded to remote storage |
System Events
| Event | Triggered When |
|---|---|
system.service_started | A system service started |
system.service_stopped | A system service stopped |
system.service_failed | A system service failed |
system.disk_warning | Disk usage exceeds threshold |
system.cpu_warning | CPU usage exceeds threshold |
system.memory_warning | Memory usage exceeds threshold |
system.update_available | Panel update available |
Quota Events
| Event | Triggered When |
|---|---|
quota.disk_warning | Disk quota approaching limit |
quota.disk_exceeded | Disk quota exceeded |
quota.bandwidth_warning | Bandwidth approaching limit |
quota.bandwidth_exceeded | Bandwidth exceeded |
quota.domain_limit_reached | Maximum domain count reached |
Monitoring Alerts
| Event | Triggered When |
|---|---|
monitor.alert_triggered | Monitoring alert threshold exceeded |
monitor.alert_resolved | Alert condition resolved |
monitor.service_down | Monitored service went down |
monitor.service_up | Monitored service recovered |
monitor.domain_down | Website unreachable |
monitor.domain_up | Website recovered |
Additional Categories:
- DNS Events --- zone creation/deletion, record changes, propagation
- Subdomain Events --- creation, update, deletion
- Database Events --- database/user creation, deletion, backup, import
- Email Events --- account creation, quota warnings, forwarding changes
- FTP Events --- account creation, login attempts
- Cron Events --- job creation, execution, failures
- File Events --- uploads, deletions, permission changes
- Docker Events --- container creation, start, stop, deletion
- WordPress Events --- installation, uninstallation
- CloudFlare Events --- DNS sync, domain sync
- Migration Events --- started, completed, failed
- SSH Events --- session start/end, login success/failure
- Terminal Events --- session management
- Plan Events --- plan creation, user assignment
- API Events --- key creation, rate limit exceeded
- Antivirus Events --- scan completed, threat detected
- Snapshot Events --- creation, restoration, deletion
---
Processing Architecture:
- Asynchronous --- Events are queued and processed by a worker pool (5 workers)
- Non-blocking --- Event queue holds up to 1,000 events
- Parallel --- Multiple webhooks for the same event are delivered concurrently
Retry Strategy (Exponential Backoff):
| Attempt | Delay | Description |
|---|---|---|
| 1st attempt | Immediate | First delivery attempt |
| 1st retry | 1 minute | First retry after failure |
| 2nd retry | 5 minutes | Second retry with increased delay |
| 3rd retry | 30 minutes | Final retry attempt |
After 3 failed retries, the delivery is marked as permanently failed.
Success Criteria:
- HTTP 2xx status code = Success
- Any other status code (3xx, 4xx, 5xx) = Failure (triggers retry)
- Connection timeout = Failure
- DNS resolution failure = Failure
---
Every webhook delivery attempt is logged with full details.
Log Entry Fields:
| Field | Description |
|---|---|
| Event Type | The event that triggered the delivery |
| Event ID | Unique event identifier (evt_uuid) |
| Status | pending, success, or failed |
| Status Code | HTTP response status code |
| Response Time | Time to receive response (milliseconds) |
| Request Payload | Full JSON payload sent |
| Response Body | Response received (max 10KB) |
| Error Message | Error details if delivery failed |
| Retry Count | Number of retry attempts made |
| Next Retry At | Scheduled time for next retry |
| Created At | When the delivery was initiated |
Actions:
- View Details --- See full request payload and response body
- Retry --- Manually retry a failed delivery
- Filter --- Filter by event type, status, or date range
---
The statistics section provides an overview of webhook health:
- Total Webhooks --- Number of configured webhooks
- Active Webhooks --- Currently enabled webhooks
- Deliveries (24h) --- Total deliveries in the last 24 hours
- Success Rate (24h) --- Percentage of successful deliveries
- Failed Deliveries (24h) --- Number of failed deliveries
- Average Response Time --- Mean response time in milliseconds
---
Available Actions:
| Action | Description |
|---|---|
| Edit | Update name, destination, events, retry settings |
| Toggle Active | Enable or disable the webhook |
| Test | Send a test event to verify the endpoint works |
| Regenerate Secret | Generate a new signing secret (old one invalidated) |
| View Logs | See delivery history for this specific webhook |
| Delete | Remove the webhook permanently |
Testing a Webhook:
- Click the Test button on any webhook
- A test event is sent with sample data
- Check the delivery logs to verify it was received
- For Telegram/Slack/Discord: check the target channel for the test message
---
1. Billing Integration (WHMCS/Blesta)
Subscribe to user and domain events to sync with your billing system:
Code:
Events: user.created, user.deleted, user.suspended,
domain.created, domain.deleted,
plan.user_assigned, plan.user_changed
2. Security Monitoring
Get instant alerts for security events:
Code:
Events: security.ip_blocked, security.brute_force_detected,
security.modsec_triggered, security.malware_detected,
user.login_failed
3. SSL Certificate Monitoring
Never miss an SSL expiration:
Code:
Events: ssl.expiring_soon, ssl.expired,
ssl.auto_renew_failed, ssl.renewed
4. Backup Monitoring
Ensure backups are running successfully:
Code:
Events: backup.created, backup.failed,
backup.restored, backup.uploaded_remote
5. Server Health Alerts (Telegram)
Real-time server status in your Telegram group:
Code:
Events: system.service_failed, system.disk_warning,
system.cpu_warning, system.memory_warning,
monitor.service_down, monitor.service_up
6. DevOps Notifications (Slack)
Development team notifications:
Code:
Events: domain.created, domain.deleted,
database.created, system.update_available,
docker.container_started, docker.container_stopped
---
| Feature | ROOT | ADMIN | RESELLER | USER |
|---|---|---|---|---|
| Create webhooks | Yes | Yes | Yes | Yes |
| View all webhooks | All | Own chain | Own | Own |
| Event subscriptions | All events | Filtered | Filtered | Own events only |
| View delivery logs | All | Own | Own | Own |
| View statistics | Global | Own | Own | Own |
---
Problem: Webhook not receiving events
- Check the webhook status is "Active" (not "Inactive" or "Failed")
- Verify the subscribed events match the events you expect
- Check delivery logs for error messages
- Test the webhook manually using the Test button
Problem: Signature verification failing
- Ensure you're using the correct webhook secret
- Read the raw body before parsing (don't parse then re-serialize)
- Use constant-time comparison for the signature check
- Check that you're removing the
sha256=prefix from the header
Problem: Deliveries failing with timeout
- Ensure your endpoint responds within the configured timeout (default 30s)
- Return HTTP 200 immediately, then process the event asynchronously
- Check network connectivity between your server and the endpoint
Problem: Telegram messages not arriving
- Verify the bot token is correct (test with Telegram API directly)
- Ensure the bot is added to the target chat/group
- Check the chat ID is correct (use @userinfobot)
- For groups: make sure the bot has permission to send messages
Problem: Duplicate events received
- This can happen during retries --- use the
event_idfield for deduplication - Store processed event IDs and skip duplicates in your handler
---
- Respond quickly --- Return HTTP 200 within 5 seconds. Process heavy tasks asynchronously after acknowledging
- Always verify signatures --- Don't trust webhook payloads without verifying the HMAC signature
- Use HTTPS endpoints --- Always use HTTPS URLs for HTTP webhook destinations
- Implement idempotency --- Handle duplicate events gracefully using
event_id - Monitor delivery logs --- Check for failed deliveries regularly
- Subscribe selectively --- Only subscribe to events you actually need
- Rotate secrets periodically --- Use the Regenerate Secret feature to rotate webhook secrets
- Use Telegram/Slack for alerts --- Quick setup for instant notifications without building an endpoint
---
Last edited: