Webhooks
Webhooks allow Ariftly to push scan results to your systems in real time — no polling required.
Setting up a webhook
- Navigate to Settings → Webhooks in the dashboard
- Click Add Endpoint
- Enter your endpoint URL
- Select the events to subscribe to
- Copy the signing secret for payload verification
Or via API:
curl -X POST https://api.ariftly.io/v1/webhooks \
-H "Authorization: Bearer $ARIFTLY_API_KEY" \
-d '{
"url": "https://myapp.example.com/webhooks/ariftly",
"events": ["scan.completed", "scan.failed"],
"description": "CI/CD pipeline notifications"
}'
Events
| Event | Trigger |
|---|---|
scan.created | A new scan has been queued |
scan.started | A scan has begun running |
scan.completed | A scan has finished successfully |
scan.failed | A scan has encountered a fatal error |
scan.cancelled | A scan was manually cancelled |
finding.new | A new finding was detected (not seen in previous scans) |
threshold.exceeded | A scan result exceeded a configured risk threshold |
Payload format
All webhook payloads share the same envelope:
{
"event": "scan.completed",
"event_id": "evt_abc123",
"created_at": "2026-03-25T10:01:45Z",
"data": { ... }
}
scan.completed payload
{
"event": "scan.completed",
"event_id": "evt_abc123",
"created_at": "2026-03-25T10:01:45Z",
"data": {
"scan_id": "scan_xyz789",
"project_id": "proj_abc123",
"status": "completed",
"risk_score": 65,
"risk_level": "high",
"detectors": {
"accessibility": { "risk_score": 30, "risk_level": "low" },
"security": { "risk_score": 82, "risk_level": "critical" }
},
"completed_at": "2026-03-25T10:01:45Z"
}
}
threshold.exceeded payload
{
"event": "threshold.exceeded",
"event_id": "evt_def456",
"created_at": "2026-03-25T10:01:45Z",
"data": {
"scan_id": "scan_xyz789",
"project_id": "proj_abc123",
"threshold": {
"detector": "security",
"limit": 50,
"actual": 82
}
}
}
Verifying webhook signatures
All webhook deliveries include a signature header. You must verify this to confirm the request came from Ariftly.
Ariftly-Signature: sha256=7d38cdd689735b008b3c702edd92eea23791c5f6
Ariftly-Timestamp: 1711362060
Verification example (Node.js)
const crypto = require('crypto');
function verifyWebhook(payload, signature, timestamp, secret) {
const body = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
const receivedHash = signature.replace('sha256=', '');
// Constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(receivedHash, 'hex')
);
}
Verification example (Python)
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
body = f"{timestamp}.{payload.decode()}"
expected = hmac.new(
secret.encode(),
body.encode(),
hashlib.sha256
).hexdigest()
received = signature.replace("sha256=", "")
return hmac.compare_digest(expected, received)
warning
Always verify webhook signatures. Reject requests where verification fails.
Also check that the Ariftly-Timestamp is within 5 minutes of the current time to prevent replay attacks.
Retries
If your endpoint returns a non-2xx response, Ariftly retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 5 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 5 minutes |
| 4th retry | 30 minutes |
| 5th retry | 2 hours |
After 5 failed attempts, the event is marked as failed and no further retries occur.
Webhook logs
The dashboard shows a delivery log for each webhook endpoint, including the request/response for each delivery attempt.