Skip to main content
surfbot.

Webhook Notifications

Configure real-time webhook notifications for scan results and security findings.

Overview

Webhooks let Surfbot push events to your systems in real-time. Instead of polling the API, you receive an HTTP POST whenever something important happens.

Setting Up Webhooks

  1. Go to Settings → Webhooks in your dashboard
  2. Click Add Webhook
  3. Enter your endpoint URL (must be HTTPS)
  4. Select which events to subscribe to
  5. Copy the signing secret for verification

Events

EventDescription
scan.startedA scan has begun
scan.completedA scan finished successfully
scan.failedA scan encountered an error
finding.newA new vulnerability or asset was discovered
finding.remediatedA previously found vulnerability is no longer detected
certificate.expiringA TLS certificate expires within 14 days
domain.verifiedDomain verification succeeded

Payload Format

All webhook payloads follow a consistent structure:

{
  "event": "finding.new",
  "timestamp": "2025-01-20T14:05:00Z",
  "data": {
    "scan_id": "scan_xyz789",
    "domain": "example.com",
    "finding": {
      "type": "vulnerability",
      "title": "CVE-2024-1234 - Remote Code Execution",
      "severity": "critical",
      "asset": "api.example.com",
      "port": 443,
      "details": "Apache HTTP Server 2.4.49 path traversal vulnerability..."
    }
  }
}

Verifying Webhooks

Every webhook request includes a signature header for verification:

X-Surfbot-Signature: sha256=abc123...

Verify it using your signing secret:

const crypto = require('crypto');
 
function verifyWebhook(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(`sha256=${expected}`)
  );
}
import hmac
import hashlib
 
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, f"sha256={expected}")

Always verify webhook signatures. Without verification, an attacker could send fake events to your endpoint.

Retry Policy

If your endpoint returns a non-2xx status code, Surfbot retries with exponential backoff:

AttemptDelay
1st retry30 seconds
2nd retry2 minutes
3rd retry10 minutes
4th retry1 hour
5th retry6 hours

After 5 failed attempts, the webhook is marked as failing and you'll receive an email notification.

Example: Slack Integration

Send new critical findings to a Slack channel:

webhook-handler.js
app.post('/webhooks/surfbot', (req, res) => {
  const { event, data } = req.body;
  
  if (event === 'finding.new' && data.finding.severity === 'critical') {
    await fetch(process.env.SLACK_WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        text: `🚨 *Critical finding on ${data.domain}*\n${data.finding.title}\nAsset: ${data.finding.asset}`,
      }),
    });
  }
  
  res.sendStatus(200);
});

Testing Webhooks

Use the Test button in your webhook settings to send a sample payload. You can also use tools like webhook.site during development to inspect payloads before building your handler.

On this page