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

  1. Go to Settings → Webhooks
  2. Click Add Webhook
  3. Enter your endpoint URL
  4. Select the events you want to receive
  5. 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

EventDescription
workflow.createdA new workflow was created
workflow.startedWorkflow execution started
workflow.stage.completedA workflow stage completed
workflow.completedWorkflow finished successfully
workflow.failedWorkflow execution failed

Security Events

EventDescription
security.scan.startedSecurity scan began
security.scan.completedSecurity scan finished
security.finding.createdNew vulnerability found
security.finding.resolvedVulnerability was resolved

Deployment Events

EventDescription
deployment.startedDeployment process began
deployment.completedDeployment successful
deployment.failedDeployment failed
deployment.rollbackDeployment was rolled back

Code Review Events

EventDescription
review.completedAI code review finished
review.comment.createdReview 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', 200

Retry Policy

If your endpoint returns a non-2xx status code, QODRYX will retry the webhook:

AttemptDelay
1st retry5 seconds
2nd retry30 seconds
3rd retry5 minutes
4th retry30 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"
  }'

Next Steps