Webhooks
Webhooks let your server receive scan status updates automatically instead of polling GET /v1/scans/:id/status. When a scan progresses, completes, or fails, we send an HTTP POST request to your registered URL.
Webhooks are coming soon. This page describes the planned API contract so you can prepare your integration ahead of time. The payload shape matches the existing scan status endpoint.
Overview
The typical scan flow without webhooks requires polling:
- Create a scan via
POST /v1/scans - Poll
GET /v1/scans/:id/statusevery few seconds - Stop polling when
statusiscompletedorerror
With webhooks, step 2 is replaced — your server receives updates automatically as they happen.
Registering a webhook
Webhooks are configured through the Enhub dashboard — not via the API. Navigate to Settings → Webhooks in your project to register, update, or delete webhook endpoints.
When creating a webhook you'll configure:
- Name
URL- Type
- string
- Description
The HTTPS URL where webhook events will be delivered. Must be publicly accessible and respond with a
2xxstatus code.
- Name
Events- Type
- string[]
- Description
Which event types to subscribe to. See Event types below.
After saving, the dashboard will display your signing secret. Store it securely — you'll need it to verify webhook signatures. The secret is only shown once upon creation.
Event types
Subscribe to any combination of these events when registering a webhook:
- Name
scan.status_update- Description
Fired when scan progression changes during processing. You may receive multiple events as the scan progresses from 0% to 100%.
- Name
scan.completed- Description
Fired once when the scan finishes successfully. The
statusfield will becompletedandprogressionwill be100.
- Name
scan.failed- Description
Fired once when the scan encounters an error. The
statusfield will beerrorandhasErrorwill betrue.
Payload format
When an event fires, we send an HTTP POST request to your registered URL with a JSON body.
Envelope
- Name
event- Type
- string
- Description
The event type (e.g.
scan.completed).
- Name
webhookId- Type
- string
- Description
The ID of the webhook registration that triggered this delivery.
- Name
timestamp- Type
- string
- Description
When the event was created (ISO 8601).
- Name
data- Type
- object
- Description
The event payload — same shape as the response from
GET /v1/scans/:id/status.
Data object
The data field contains a scan status object with these properties:
- Name
id- Type
- string
- Description
Scan ID.
- Name
status- Type
- string
- Description
Current status. One of:
processing,completed,error.
- Name
progression- Type
- number
- Description
Progression percentage (0–100).
- Name
hasError- Type
- boolean
- Description
Whether the scan has an error.
- Name
errorMessage- Type
- string
- Description
Error message (only present when
hasErroristrue).
- Name
createdAt- Type
- string
- Description
When the scan was created (ISO 8601).
scan.completed
{
"event": "scan.completed",
"webhookId": "wh_cml3ucftb0001yqzvr4jgakw5",
"timestamp": "2026-03-01T12:05:32.000Z",
"data": {
"id": "cml3ucftb0001yqzvr4jgakw5",
"status": "completed",
"progression": 100,
"hasError": false,
"createdAt": "2026-03-01T12:00:00.000Z"
}
}
scan.failed
{
"event": "scan.failed",
"webhookId": "wh_cml3ucftb0001yqzvr4jgakw5",
"timestamp": "2026-03-01T12:05:32.000Z",
"data": {
"id": "cml3ucftb0001yqzvr4jgakw5",
"status": "error",
"progression": 45,
"hasError": true,
"errorMessage": "Scan timed out na 12 minuten. De berekening duurde te lang — probeer het opnieuw.",
"createdAt": "2026-03-01T12:00:00.000Z"
}
}
scan.status_update
{
"event": "scan.status_update",
"webhookId": "wh_cml3ucftb0001yqzvr4jgakw5",
"timestamp": "2026-03-01T12:02:15.000Z",
"data": {
"id": "cml3ucftb0001yqzvr4jgakw5",
"status": "processing",
"progression": 45,
"hasError": false,
"createdAt": "2026-03-01T12:00:00.000Z"
}
}
Verifying signatures
Every webhook delivery includes an X-Enhub-Signature header containing an HMAC-SHA256 signature of the raw request body, using your webhook secret as the key. Always verify this signature to ensure the request is from Enhub.
Verification steps
- Read the raw request body (before JSON parsing)
- Compute HMAC-SHA256 of the body using your webhook secret
- Compare the result with the
X-Enhub-Signatureheader value - Reject the request if the signatures don't match
Signature verification
import crypto from 'crypto';
function verifyWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}
// Express.js example
app.post('/webhooks/enhub', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-enhub-signature'];
const secret = process.env.ENHUB_WEBHOOK_SECRET;
if (!verifyWebhook(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log(`Received ${event.event} for scan ${event.data.id}`);
// Process the event...
res.status(200).send('OK');
});
Retry policy
If your endpoint doesn't return a 2xx status code, we retry the delivery with exponential backoff:
- Name
Attempt 1- Description
Immediate delivery.
- Name
Attempt 2- Description
30 seconds after the first attempt.
- Name
Attempt 3- Description
5 minutes after the second attempt.
- Name
Attempt 4- Description
30 minutes after the third attempt.
After 4 failed attempts, the delivery is marked as failed. If a webhook endpoint consistently fails, it will be automatically deactivated after 10 consecutive failures.
Your endpoint should respond within 10 seconds. If the response takes longer, the delivery will time out and count as a failure.
Managing webhooks
Webhooks are managed entirely through the Enhub dashboard under Settings → Webhooks. From there you can:
- View all registered webhooks and their delivery history
- Edit the URL, subscribed events, or active status
- Pause deliveries by toggling a webhook to inactive
- Delete a webhook registration permanently
- Rotate the signing secret if it has been compromised