Webhooks API
Webhooks let you receive real-time notifications when events occur in Infracast. When a new critical finding is detected, a discovery job completes, or a report is ready, Infracast sends an HTTP POST to your configured endpoint. Webhooks are configured via the Integrations API.
Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /api/v1/tenants/{tenantID}/integrations | List integrations (including webhooks) | tenant:read |
| POST | /api/v1/tenants/{tenantID}/integrations | Create a webhook | tenant:update |
| GET | /api/v1/tenants/{tenantID}/integrations/{integrationID} | Get webhook config | tenant:read |
| PUT | /api/v1/tenants/{tenantID}/integrations/{integrationID} | Update webhook | tenant:update |
| DELETE | /api/v1/tenants/{tenantID}/integrations/{integrationID} | Delete webhook | tenant:update |
Creating a Webhook
curl -X POST https://api.infracast.io/api/v1/tenants/acme-corp/integrations \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "webhook",
"name": "Security Alerts — Slack Relay",
"config": {
"url": "https://your-app.com/infracast/webhook",
"secret": "your-webhook-signing-secret",
"events": [
"finding.created",
"finding.severity_changed",
"job.completed",
"job.failed"
],
"filters": {
"severity": ["CRITICAL", "HIGH"]
},
"headers": {
"X-Source": "infracast",
"X-Environment": "production"
}
}
}'
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
type | string | ✅ | Must be "webhook" |
name | string | ✅ | Human-readable name |
config.url | string | ✅ | HTTPS endpoint to POST events to |
config.secret | string | ✅ | Signing secret for HMAC verification |
config.events | array | ✅ | Event types to subscribe to |
config.filters | object | ❌ | Optional filters to reduce event volume |
config.headers | object | ❌ | Additional HTTP headers to include |
config.timeout_seconds | int | ❌ | Request timeout (default: 30) |
Response
{
"id": "intg-abc123def456",
"type": "webhook",
"name": "Security Alerts — Slack Relay",
"status": "active",
"config": {
"url": "https://your-app.com/infracast/webhook",
"events": ["finding.created", "finding.severity_changed", "job.completed", "job.failed"],
"filters": { "severity": ["CRITICAL", "HIGH"] }
},
"created_at": "2024-03-16T10:00:00Z"
}
The webhook secret is only returned on creation. Store it securely (e.g., in your secrets manager). You'll need it to verify webhook signatures.
Event Types
Finding Events
| Event | Trigger |
|---|---|
finding.created | A new compliance finding is detected |
finding.resolved | A previously open finding is no longer triggered |
finding.severity_changed | Finding severity upgraded or downgraded |
finding.risk_accepted | Finding marked as accepted risk |
finding.reopened | A resolved finding is detected again |
Discovery Job Events
| Event | Trigger |
|---|---|
job.created | A discovery job is created |
job.started | A discovery job begins running |
job.completed | A discovery job finished successfully |
job.failed | A discovery job failed |
job.cancelled | A discovery job was cancelled |
Report Events
| Event | Trigger |
|---|---|
report.generated | A report is ready to download |
report.failed | Report generation failed |
report.scheduled | A scheduled report delivery is sent |
Compliance Events
| Event | Trigger |
|---|---|
compliance.score_changed | Overall compliance score changed by ≥5% |
compliance.control_failed | A previously passing control is now failing |
compliance.control_passed | A previously failing control is now passing |
Webhook Payload Format
All events share a common envelope structure:
{
"id": "evt-abc123def456",
"type": "finding.created",
"tenant_id": "acme-corp",
"timestamp": "2024-03-16T10:05:23Z",
"data": { ... }
}
finding.created Payload
{
"id": "evt-abc123def456",
"type": "finding.created",
"tenant_id": "acme-corp",
"timestamp": "2024-03-16T10:05:23Z",
"data": {
"finding": {
"id": "CIS-AWS-5.2-aws:us-east-1:aws.ec2.security_group:sg-0abc123",
"rule_id": "CIS-AWS-5.2",
"framework": "cis-aws",
"control_id": "5.2",
"severity": "HIGH",
"title": "Security group allows unrestricted SSH access",
"description": "Security group 'web-servers-sg' has an inbound rule for SSH from 0.0.0.0/0.",
"resource_id": "aws:us-east-1:aws.ec2.security_group:sg-0abc123",
"resource_type": "aws.ec2.security_group",
"resource_name": "web-servers-sg",
"region": "us-east-1",
"remediation": "Remove the 0.0.0.0/0 inbound rule for port 22.",
"created_at": "2024-03-16T10:05:23Z"
}
}
}
job.completed Payload
{
"id": "evt-xyz789",
"type": "job.completed",
"tenant_id": "acme-corp",
"timestamp": "2024-03-16T10:03:07Z",
"data": {
"job": {
"id": "job-abc123def456",
"name": "Full AWS Discovery",
"plugins": ["aws"],
"status": "completed",
"stats": {
"nodes_discovered": 1247,
"nodes_updated": 312,
"findings_created": 34,
"findings_resolved": 12,
"duration_seconds": 187
},
"started_at": "2024-03-16T10:00:00Z",
"completed_at": "2024-03-16T10:03:07Z"
}
}
}
compliance.score_changed Payload
{
"id": "evt-score001",
"type": "compliance.score_changed",
"tenant_id": "acme-corp",
"timestamp": "2024-03-16T10:05:30Z",
"data": {
"framework": "nist-800-53",
"previous_score": 87,
"current_score": 79,
"delta": -8,
"triggered_by": "job-abc123def456",
"new_failures": 4,
"new_passes": 0
}
}
report.generated Payload
{
"id": "evt-rpt001",
"type": "report.generated",
"tenant_id": "acme-corp",
"timestamp": "2024-03-16T10:01:30Z",
"data": {
"report": {
"id": "rpt-abc123",
"type": "framework",
"framework": "pci-dss",
"format": "pdf",
"download_url": "https://api.infracast.io/api/v1/tenants/acme-corp/reports/rpt-abc123/download",
"download_expires": "2024-03-23T10:01:30Z",
"size_bytes": 2847392
}
}
}
Signature Verification
Infracast signs every webhook request using HMAC-SHA256. Always verify the signature before processing the payload to ensure the request is authentic.
Signature Header
X-Infracast-Signature: sha256=2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
X-Infracast-Timestamp: 1710590400
X-Infracast-Event: finding.created
Verification (Python)
import hmac
import hashlib
import time
def verify_webhook(secret: str, payload: bytes, signature: str, timestamp: str) -> bool:
# Reject requests older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return False
# Compute expected signature
message = f"{timestamp}.{payload.decode('utf-8')}"
expected = hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Compare (constant-time)
return hmac.compare_digest(f"sha256={expected}", signature)
# In your webhook handler:
@app.route('/infracast/webhook', methods=['POST'])
def handle_webhook():
if not verify_webhook(
secret=os.environ['WEBHOOK_SECRET'],
payload=request.data,
signature=request.headers['X-Infracast-Signature'],
timestamp=request.headers['X-Infracast-Timestamp']
):
return "Unauthorized", 401
event = request.json
if event['type'] == 'finding.created':
finding = event['data']['finding']
if finding['severity'] in ('CRITICAL', 'HIGH'):
send_slack_alert(finding)
return "OK", 200
Verification (Node.js)
const crypto = require('crypto');
function verifyWebhook(secret, payload, signature, timestamp) {
// Reject old requests
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false;
const message = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expected}`),
Buffer.from(signature)
);
}
Filtering Events
Reduce webhook noise with event filters:
{
"config": {
"url": "https://your-app.com/webhook",
"events": ["finding.created"],
"filters": {
"severity": ["CRITICAL", "HIGH"],
"frameworks": ["nist-800-53", "fedramp-high"],
"resource_types": ["aws.s3.bucket", "aws.ec2.security_group"]
}
}
}
Retry Policy
If your endpoint returns a non-2xx status or times out, Infracast retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 (initial) | Immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the event is marked as permanently failed and the webhook is automatically disabled if failure rate exceeds 50%.
Respond with 200 OK immediately and process events asynchronously (queue them). This prevents timeouts from triggering retries for events you did receive.
Other Integration Types
Beyond webhooks, Infracast supports direct integrations with popular tools via the same Integrations API:
| Integration Type | type value | Purpose |
|---|---|---|
| Webhook (HTTP) | webhook | Custom HTTP endpoint |
| Slack | slack | Slack channel alerts |
| PagerDuty | pagerduty | Incident creation |
| ServiceNow | servicenow | Ticket creation |
| Jira | jira | Issue creation |
| Microsoft Teams | teams | Teams channel alerts |
email | Email notifications |
Next Steps
- Findings API — The findings that trigger
finding.*events - Discovery Jobs API — Jobs that trigger
job.*events - Reports API — Reports that trigger
report.*events