Webhooks
Receive real-time event notifications
Overview
Webhooks allow you to receive real-time HTTP notifications when events happen in your QODRYX account. Instead of polling the API, you can configure webhooks to push events to your application.
Setting Up Webhooks
Via Dashboard
- Go to Settings → Webhooks
- Click Add Webhook
- Enter your endpoint URL
- Select the events you want to receive
- Copy the signing secret for verification
Via API
curl -X POST "https://api.qodryx.com/v1/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/qodryx",
"events": [
"workflow.completed",
"workflow.failed",
"security.scan.completed",
"deployment.completed"
],
"active": true
}'
# Response
{
"data": {
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/qodryx",
"events": [...],
"secret": "whsec_xyz789...",
"active": true,
"created_at": "2024-01-15T10:00:00Z"
}
}Event Types
Workflow Events
| Event | Description |
|---|---|
| workflow.created | A new workflow was created |
| workflow.started | Workflow execution started |
| workflow.stage.completed | A workflow stage completed |
| workflow.completed | Workflow finished successfully |
| workflow.failed | Workflow execution failed |
Security Events
| Event | Description |
|---|---|
| security.scan.started | Security scan began |
| security.scan.completed | Security scan finished |
| security.finding.created | New vulnerability found |
| security.finding.resolved | Vulnerability was resolved |
Deployment Events
| Event | Description |
|---|---|
| deployment.started | Deployment process began |
| deployment.completed | Deployment successful |
| deployment.failed | Deployment failed |
| deployment.rollback | Deployment was rolled back |
Code Review Events
| Event | Description |
|---|---|
| review.completed | AI code review finished |
| review.comment.created | Review comment added |
Webhook Payload
All webhook payloads follow this structure:
{
"id": "evt_abc123",
"type": "workflow.completed",
"created_at": "2024-01-15T14:30:00Z",
"data": {
"workflow_id": "wf_xyz789",
"project_id": "proj_abc123",
"status": "completed",
"duration_ms": 45000,
"stages": {
"intake": "completed",
"build": "completed",
"verify": "completed",
"prove": "completed",
"iterate": "completed",
"package": "completed"
},
"outputs": {
"docker_image": "gcr.io/project/app:v1.2.3",
"deployment_url": "https://app.example.com"
}
}
}Verifying Webhooks
All webhooks are signed with your webhook secret. Verify the signature to ensure the request came from QODRYX:
Always Verify Signatures
Never process webhooks without verifying the signature to prevent spoofing attacks.
Signature Header
The X-Qodryx-Signature header contains the HMAC-SHA256 signature:
X-Qodryx-Signature: t=1705326600,v1=abc123def456...Verification Examples
Node.js
import crypto from 'crypto';
function verifyWebhook(payload, signature, secret) {
const elements = signature.split(',');
const timestamp = elements.find(e => e.startsWith('t=')).slice(2);
const expectedSig = elements.find(e => e.startsWith('v1=')).slice(3);
const signedPayload = `${timestamp}.${payload}`;
const computedSig = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(expectedSig),
Buffer.from(computedSig)
);
}
// Express middleware
app.post('/webhooks/qodryx', (req, res) => {
const signature = req.headers['x-qodryx-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const event = req.body;
console.log('Received event:', event.type);
res.status(200).send('OK');
});Python
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
elements = dict(e.split('=') for e in signature.split(','))
timestamp = elements['t']
expected_sig = elements['v1']
signed_payload = f"{timestamp}.{payload.decode()}"
computed_sig = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_sig, computed_sig)
# Flask example
@app.route('/webhooks/qodryx', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Qodryx-Signature')
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.json
print(f"Received event: {event['type']}")
return 'OK', 200Retry Policy
If your endpoint returns a non-2xx status code, QODRYX will retry the webhook:
| Attempt | Delay |
|---|---|
| 1st retry | 5 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 5 minutes |
| 4th retry | 30 minutes |
| 5th retry (final) | 2 hours |
After 5 failed attempts, the webhook is marked as failed. You'll receive an email notification and can view failed webhooks in the dashboard.
Best Practices
Recommendations
- Return 200 status quickly, process webhooks asynchronously
- Always verify the webhook signature before processing
- Handle duplicate events (use event ID for idempotency)
- Use HTTPS endpoints only
- Subscribe only to events you need
Testing Webhooks
Test your webhook endpoint before deploying:
# Send a test webhook from the dashboard
# Settings → Webhooks → Select webhook → Send Test
# Or via API
curl -X POST "https://api.qodryx.com/v1/webhooks/wh_abc123/test" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"event_type": "workflow.completed"
}'