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

EventDescription
agent.deployedAgent successfully deployed
agent.failedAgent deployment or runtime failure
agent.startedAgent started
agent.stoppedAgent stopped
agent.scaledAgent auto-scaled up or down
agent.health_changedAgent health status changed
chain.completedChain execution completed
chain.failedChain execution failed
security.threat_blockedShellGuard blocked a threat
billing.threshold_reachedUsage threshold reached
billing.payment_failedPayment 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', 200

Verification 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:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 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