Webhooks
Webhooks allow you to receive real-time notifications when events occur in your Fegura account. Instead of polling the API, webhooks push event data directly to your server as they happen.
How Webhooks Work
- You register a webhook endpoint URL in your Fegura settings
- Subscribe to the events you want to receive
- When an event occurs, Fegura sends an HTTP POST request to your URL
- Your server processes the event and returns a 2xx response
Event Types
Fegura supports the following webhook events:
| Event | Description |
|---|---|
scan.completed | Fired when an infrastructure scan finishes successfully |
diagram.generated | Fired when a diagram is generated from a scan |
Webhook Payload
All webhook payloads follow a consistent structure with the event type, timestamp, and event-specific data:
{
"event": "scan.completed",
"timestamp": "2026-01-20T14:32:15.000Z",
"data": {
"scanId": "660e8400-e29b-41d4-a716-446655440000",
"accountId": "550e8400-e29b-41d4-a716-446655440000",
"resourceCount": 47,
"duration": 125000
}
}Common Fields
| Parameter | Type | Description |
|---|---|---|
event | string | The event type that triggered this webhook |
timestamp | string | ISO 8601 timestamp when the event occurred |
data | object | Event-specific payload data |
scan.completed Data
| Parameter | Type | Description |
|---|---|---|
scanId | string | ID of the completed scan |
accountId | string | Fegura account connection ID |
resourceCount | integer | Number of resources discovered |
duration | integer | Scan duration in milliseconds |
diagram.generated Data
| Parameter | Type | Description |
|---|---|---|
diagramId | string | ID of the generated diagram |
scanId | string | ID of the scan that produced this diagram |
accountId | string | Fegura account connection ID |
type | string | Diagram type (architecture, network, dataflow, boundary, organization) |
Webhook Signatures
Every webhook request includes a signature header that you should verify to ensure the request came from Fegura. This protects against spoofed requests.
Webhook Headers
Every webhook request includes the following headers:
| Parameter | Type | Description |
|---|---|---|
X-Fegura-Signature | string | HMAC-SHA256 signature of the request body (sha256=...) |
X-Fegura-Event | string | The event type (e.g., scan.completed, diagram.generated) |
X-Fegura-Delivery-Id | string | Unique delivery ID for idempotency (UUID) |
X-Fegura-Signature: sha256=5d41402abc4b2a76b9719d911017c592
X-Fegura-Event: scan.completed
X-Fegura-Delivery-Id: 880e8400-e29b-41d4-a716-446655440000Verifying Signatures
To verify a webhook signature, compute the HMAC-SHA256 hash of the raw request body using your webhook secret, then compare it to the signature header.
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
// Use constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
app.post('/webhooks/fegura', (req, res) => {
const signature = req.headers['x-fegura-signature'];
const payload = req.rawBody; // Raw request body as string
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const event = JSON.parse(payload);
console.log('Received event:', event.event);
res.status(200).send('OK');
});Python Example
import hmac
import hashlib
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# In your Flask webhook handler
@app.route('/webhooks/fegura', methods=['POST'])
def webhook_handler():
signature = request.headers.get('X-Fegura-Signature')
payload = request.get_data()
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
print(f"Received event: {event['event']}")
return 'OK', 200Retry Policy
If your endpoint doesn't return a 2xx status code, Fegura will automatically retry delivery with exponential backoff. Retries are managed by an SQS queue, so you don't need to configure anything on your end.
Best Practices
- Always verify signatures - Never process webhooks without verifying the signature first.
- Respond quickly - Return a 2xx response as soon as possible. Process the event asynchronously if needed.
- Handle duplicates - Webhooks may be delivered more than once. Use the event ID for idempotency.
- Use HTTPS - Webhook endpoints must use HTTPS for security.
- Monitor failures - Check your webhook settings for delivery failures and errors.
Managing Webhooks
Webhooks can be managed via the API or from your Webhook Settings page.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/webhooks | Create a new webhook endpoint |
| GET | /api/webhooks | List all webhook endpoints |
| GET | /api/webhooks/{id} | Get a specific webhook |
| PATCH | /api/webhooks/{id} | Update a webhook endpoint |
| DELETE | /api/webhooks/{id} | Delete a webhook endpoint |
| POST | /api/webhooks/{id}/regenerate-secret | Regenerate the webhook signing secret |
| POST | /api/webhooks/{id}/test | Send a test webhook event |