Webhooks
Receive real-time notifications about events in your ShellHub account via HTTP webhooks.
// How Webhooks Work
Event Occurs
→Agent deployed, failed, etc.
ShellHub
→Creates webhook payload
HTTP POST
→To your endpoint
Your App
Processes event
// Available Events
| Event | Description |
|---|---|
agent.deployed | Agent successfully deployed |
agent.failed | Agent deployment or runtime failure |
agent.started | Agent started |
agent.stopped | Agent stopped |
agent.scaled | Agent auto-scaled up or down |
agent.health_changed | Agent health status changed |
chain.completed | Chain execution completed |
chain.failed | Chain execution failed |
security.threat_blocked | ShellGuard blocked a threat |
billing.threshold_reached | Usage threshold reached |
billing.payment_failed | Payment processing failed |
// Creating Webhooks
Via API
curl -X POST https://api.shellhub.app/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/shellhub",
"events": [
"agent.deployed",
"agent.failed",
"agent.health_changed"
],
"secret": "your-webhook-secret",
"active": true
}'Via CLI
shellhub webhooks create \ --url https://yourapp.com/webhooks/shellhub \ --events agent.deployed,agent.failed \ --secret my-secret
// Webhook Payload
All webhooks are sent as HTTP POST requests with a JSON payload:
{
"id": "evt_abc123xyz",
"type": "agent.deployed",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"agent_id": "agent_def456",
"agent_name": "my-agent",
"cloud_id": "cloud_xyz789",
"version": "1.2.0",
"status": "running",
"endpoint": "https://my-cloud.shellhub.app/my-agent"
},
"meta": {
"account_id": "acc_123",
"webhook_id": "wh_abc123"
}
}Event-Specific Payloads
agent.failed
"data": {
"agent_id": "agent_def456",
"agent_name": "my-agent",
"error": {
"code": "BUILD_FAILED",
"message": "Failed to install dependencies",
"details": "pip install failed with exit code 1"
},
"logs_url": "https://app.shellhub.app/logs/..."
}agent.scaled
"data": {
"agent_id": "agent_def456",
"previous_instances": 2,
"current_instances": 5,
"trigger": "cpu_threshold",
"cpu_utilization": 85.5
}security.threat_blocked
"data": {
"threat_type": "sql_injection",
"severity": "high",
"source_ip": "185.234.x.x",
"target_agent": "my-agent",
"target_endpoint": "/api/users",
"blocked_at": "2024-01-15T10:30:00Z"
}// Verifying Webhooks
All webhook requests include a signature header for verification:
X-ShellHub-Signature: sha256=abc123... X-ShellHub-Timestamp: 1705312200
Verification Example (Python)
import hmac
import hashlib
import time
def verify_webhook(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
# Check timestamp is within 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return False
# Compute expected signature
message = f"{timestamp}.{payload.decode()}"
expected = hmac.new(
secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
# Compare signatures
expected_sig = f"sha256={expected}"
return hmac.compare_digest(signature, expected_sig)
# Flask example
@app.route('/webhooks/shellhub', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-ShellHub-Signature')
timestamp = request.headers.get('X-ShellHub-Timestamp')
if not verify_webhook(request.data, signature, timestamp, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.json
# Process event...
return 'OK', 200Verification Example (Node.js)
const crypto = require('crypto');
function verifyWebhook(payload, signature, timestamp, secret) {
// Check timestamp
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
return false;
}
// Compute signature
const message = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
return signature === `sha256=${expected}`;
}
// Express example
app.post('/webhooks/shellhub', (req, res) => {
const signature = req.headers['x-shellhub-signature'];
const timestamp = req.headers['x-shellhub-timestamp'];
if (!verifyWebhook(req.rawBody, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
// Process event...
res.status(200).send('OK');
});// Retry Behavior
If your endpoint returns a non-2xx status code, ShellHub will retry the webhook:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry (final) | 24 hours |
Tip: Your endpoint should return 200 OK quickly, even if you need to process the event asynchronously. Use a message queue for long-running tasks.
Best Practices
- ✓Always verify signatures - Don't trust unverified webhooks
- ✓Respond quickly - Return 200 within 5 seconds
- ✓Handle duplicates - Use event IDs for idempotency
- ✓Use HTTPS - Always use HTTPS endpoints
- ✓Monitor failures - Set up alerts for webhook failures