Skip to Content

Webhooks

Webhooks let Quazzar Cloud OS send real-time event notifications to your systems. When something happens on the instance — an app is installed, a backup completes, an alert fires — Cloud OS sends an HTTP POST request to your configured endpoint.

Setup

Creating a Webhook via the API

curl -X POST http://localhost:8080/api/webhooks \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhooks/quazzar", "events": ["app.installed", "app.crashed", "backup.completed"], "active": true }'

Returns the webhook configuration with a signing secret:

{ "id": "wh_abc123", "url": "https://your-app.com/webhooks/quazzar", "events": ["app.installed", "app.crashed", "backup.completed"], "secret": "whsec_abc123...", "active": true, "created_at": "2025-01-15T10:30:00Z" }

Copy the signing secret immediately. It is only shown once at creation time. You will need it to verify webhook signatures.

Creating a Webhook via the UI

  1. Open the Cloud OS dashboard.
  2. Navigate to Settings then Webhooks.
  3. Click Add Webhook.
  4. Enter the target URL (HTTPS recommended).
  5. Select which events to subscribe to or choose “All events”.
  6. Copy the generated signing secret and store it securely.
  7. Click Save.

Managing Webhooks

# List all webhooks curl http://localhost:8080/api/webhooks \ -H "Authorization: Bearer <token>" # Update a webhook curl -X PUT http://localhost:8080/api/webhooks/wh_abc123 \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "events": ["app.installed", "app.crashed", "backup.completed", "alert.fired"], "active": true }' # Delete a webhook curl -X DELETE http://localhost:8080/api/webhooks/wh_abc123 \ -H "Authorization: Bearer <token>"

Event Types

EventTrigger
app.installedA new app is installed from a template
app.startedAn app container is started
app.stoppedAn app container is stopped
app.crashedAn app container exits unexpectedly
app.uninstalledAn app is removed from the instance
app.updatedAn app is updated to a new version
backup.startedA backup operation begins
backup.completedA backup finishes successfully
backup.failedA backup operation fails
backup.restoredA restore operation completes
alert.firedAn alert rule threshold is breached
alert.resolvedAn alert condition returns to normal
domain.addedA custom domain is configured
domain.ssl_provisionedAn SSL certificate is issued for a domain
domain.ssl_expiringAn SSL certificate is nearing expiration
vpn.peer_addedA new VPN peer is created
vpn.peer_removedA VPN peer is deleted
security.scan_completedA security scan finishes
security.finding_criticalA critical security finding is detected
system.disk_warningDisk usage exceeds the warning threshold
system.update_availableA new Cloud OS version is available

Payload Format

Every webhook request is an HTTP POST with a JSON body:

{ "id": "evt_abc123", "type": "app.installed", "timestamp": "2025-01-15T10:30:00Z", "instance_id": "node_abc123", "data": { "app_id": "app_def456", "name": "my-nextcloud", "template": "nextcloud", "status": "running" } }
FieldDescription
idUnique event identifier for idempotency
typeEvent type string (see table above)
timestampISO 8601 timestamp of when the event occurred
instance_idThe Cloud OS node ID that generated the event
dataEvent-specific payload containing the relevant resource

The data field varies by event type and contains the full resource object relevant to the event.


HMAC-SHA256 Signature Verification

Every webhook request includes an X-Quazzar-Signature header containing an HMAC-SHA256 signature of the raw request body, computed using your webhook signing secret.

X-Quazzar-Signature: sha256=a1b2c3d4e5f6...

Always verify the signature before processing the payload to prevent forged events.

Verification in Go

package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "strings" ) func verifySignature(payload []byte, signature string, secret string) bool { mac := hmac.New(sha256.New, []byte(secret)) mac.Write(payload) expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signature)) }

Verification in Python

import hmac import hashlib def verify_signature(payload: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode("utf-8"), payload, hashlib.sha256, ).hexdigest() return hmac.compare_digest(f"sha256={expected}", signature)

Verification in Node.js

const crypto = require('crypto'); function verifySignature(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload, 'utf-8') .digest('hex'); return crypto.timingSafeEqual( Buffer.from('sha256=' + expected), Buffer.from(signature), ); }

Always use constant-time comparison (like hmac.compare_digest in Python or crypto.timingSafeEqual in Node.js) when verifying signatures. Simple string comparison is vulnerable to timing attacks.


Retry Policy

If your endpoint returns a non-2xx status code or the request times out (30-second timeout), Cloud OS retries delivery with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry12 hours

After 5 failed retries, the event is marked as failed. You can view failed deliveries and manually retry them from the Cloud OS dashboard under Settings then Webhooks then Delivery Log.

If a webhook endpoint fails 10 consecutive deliveries, the webhook is automatically deactivated. An alert.fired event with type webhook_disabled is generated (delivered to other active webhooks) so you are notified.


Idempotency

Each event includes a unique id field. Use this to deduplicate events on your end in case of retries. If you receive an event with an id you have already processed, acknowledge it with a 200 response but skip processing.


Example Handler

A minimal Express.js webhook handler:

const express = require('express'); const crypto = require('crypto'); const app = express(); app.use(express.raw({ type: 'application/json' })); const WEBHOOK_SECRET = process.env.QUAZZAR_WEBHOOK_SECRET; app.post('/webhooks/quazzar', (req, res) => { const signature = req.headers['x-quazzar-signature']; const expected = crypto .createHmac('sha256', WEBHOOK_SECRET) .update(req.body) .digest('hex'); if (!crypto.timingSafeEqual( Buffer.from('sha256=' + expected), Buffer.from(signature) )) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body); console.log('Received event:', event.type, 'id:', event.id); switch (event.type) { case 'app.crashed': // Trigger incident response break; case 'backup.completed': // Update backup tracking system break; case 'alert.fired': // Forward to PagerDuty / Opsgenie break; default: console.log('Unhandled event type:', event.type); } res.status(200).send('OK'); }); app.listen(3000);

Tips

  • Use a message queue (Redis, RabbitMQ, SQS) to process webhook events asynchronously so your handler responds within the 30-second timeout.
  • Store the event id to implement idempotency and safely handle retries.
  • Subscribe only to the events you need to reduce noise and processing overhead.
  • Test your webhook endpoint using the Send Test Event button in the Cloud OS dashboard before relying on it in production.
  • Monitor the delivery log periodically for failed events that may need manual retry.