Webhooks
Webhooks allow you to receive real-time HTTP notifications when events occur in your fleet. Perfect for alerting, automation, and integrating with external systems.
Supported Events
| Event | Description | When it fires |
|---|---|---|
host.enrolled |
New host joins fleet | First time a Shadow agent connects with your org token |
host.online |
Host comes online | Agent reconnects after being offline |
host.offline |
Host goes offline | Agent disconnects or stops responding |
query.completed |
Query finishes | A distributed query completes across all targeted hosts |
Creating a Webhook
Create a webhook endpoint via the API:
curl -X POST https://hyprwatch.cloud/api/v1/webhooks \
-H "Authorization: Bearer hw_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhook",
"events": ["host.enrolled", "host.online", "host.offline"]
}'
Response:
{
"id": "wh_abc123",
"url": "https://your-app.com/webhook",
"events": ["host.enrolled", "host.online", "host.offline"],
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxx",
"active": true,
"created_at": "2024-01-15T10:30:00Z"
}
Webhook Payload
When an event occurs, we send a POST request to your URL with a JSON payload:
{
"id": "evt_xyz789",
"event": "host.online",
"timestamp": "2024-01-15T10:35:00Z",
"data": {
"host_id": "h_abc123",
"hostname": "server-01",
"platform": "linux",
"os_version": "Ubuntu 22.04",
"ip_address": "192.168.1.100"
}
}
Signature Verification
Every webhook request includes a signature header for verification:
X-Hyprwatch-Signature: sha256=a1b2c3d4e5f6...
Verify the signature by computing HMAC-SHA256 of the raw request body using your webhook secret:
Python Example
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
# In your webhook handler:
signature = request.headers.get("X-Hyprwatch-Signature")
if not verify_signature(request.body, signature, WEBHOOK_SECRET):
return Response(status=401)
Node.js Example
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
// Express middleware
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-hyprwatch-signature'];
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook...
});
Example Payloads
host.enrolled
{
"id": "evt_001",
"event": "host.enrolled",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"host_id": "h_abc123",
"hostname": "new-server",
"platform": "linux",
"os_version": "Ubuntu 22.04 LTS",
"osquery_version": "5.10.2",
"shadow_version": "0.1.0"
}
}
host.online / host.offline
{
"id": "evt_002",
"event": "host.online",
"timestamp": "2024-01-15T10:35:00Z",
"data": {
"host_id": "h_abc123",
"hostname": "server-01",
"platform": "linux",
"last_seen": "2024-01-15T10:35:00Z",
"previous_state": "offline",
"offline_duration_seconds": 3600
}
}
query.completed
Fired when a scheduled query runs on a host. Each host sends its own webhook with results.
{
"id": "evt_003",
"event": "query.completed",
"timestamp": "2024-01-15T10:40:00Z",
"data": {
"query_id": "sq_abc123",
"query_name": "unauthorized_ssh_keys",
"sql": "SELECT * FROM authorized_keys WHERE ...",
"result_id": "r_xyz789",
"host_id": "h_def456",
"hostname": "prod-server-01",
"platform": "linux",
"row_count": 2,
"rows": [
{"key": "ssh-rsa AAAA...", "key_file": "/root/.ssh/authorized_keys"},
{"key": "ssh-rsa BBBB...", "key_file": "/root/.ssh/authorized_keys"}
],
"rows_truncated": false
}
}
rows_truncated
will be true.
Managing Webhooks
/webhooks
List all webhooks for your organization.
/webhooks/:id
Get details about a specific webhook, including delivery history.
/webhooks/:id
Update webhook URL, events, or active status.
/webhooks/:id
Delete a webhook.
/webhooks/:id/rotate-secret
Generate a new webhook secret. The old secret is invalidated immediately.
Retry Policy
If your endpoint returns a non-2xx status code or times out, we retry with exponential backoff:
- Attempt 1: Immediate
- Attempt 2: 1 minute delay
- Attempt 3: 5 minutes delay
- Attempt 4: 30 minutes delay
- Attempt 5: 2 hours delay (final)
After 5 failed attempts, the webhook is marked as failed and disabled. You can re-enable it from the dashboard or API.
Integration Examples
Slack Notification
import requests
def handle_webhook(event):
if event["event"] == "host.offline":
host = event["data"]
requests.post(SLACK_WEBHOOK_URL, json={
"text": f":warning: Host *{host['hostname']}* went offline",
"attachments": [{
"color": "warning",
"fields": [
{"title": "Platform", "value": host["platform"], "short": True},
{"title": "Last Seen", "value": host["last_seen"], "short": True}
]
}]
})
PagerDuty Alert
import requests
def handle_webhook(event):
if event["event"] == "host.offline":
host = event["data"]
# Only alert for critical hosts
if "production" in host.get("labels", []):
requests.post("https://events.pagerduty.com/v2/enqueue", json={
"routing_key": PAGERDUTY_KEY,
"event_action": "trigger",
"payload": {
"summary": f"Production host {host['hostname']} offline",
"severity": "critical",
"source": "hyprwatch",
"custom_details": host
}
})
Best Practices
Always verify signatures
Never process webhook payloads without verifying the HMAC signature. This prevents replay attacks and payload tampering.
Respond quickly
Return a 2xx response within 30 seconds. Process the payload asynchronously if needed.
Handle duplicates
Use the event id
field to deduplicate. Retries may deliver the same event multiple times.
Use HTTPS
Webhook URLs must use HTTPS. We do not deliver to HTTP endpoints in production.