API

Webhooks

CDA Webhooks deliver real-time event notifications to your endpoints when actions occur in the platform.

Configuration:

Webhooks are managed per organization from the C3 Command Post at Settings > Webhooks, or via the REST API:

POST /rest/v1/webhook_endpoints Creates a new webhook endpoint.

Request body: { "org_id": "your-org-uuid", "url": "https://your-app.com/webhooks/cda", "events": ["scan.completed", "mission.assigned", "mission.completed"], "is_active": true }

Response (201 Created): { "id": "uuid", "org_id": "uuid", "url": "https://your-app.com/webhooks/cda", "events": ["scan.completed", "mission.assigned", "mission.completed"], "secret": "whsec_...", "is_active": true, "created_at": "2026-03-22T00:00:00Z" }

Available event types: - scan.completed: A recon scan finished processing - scan.claimed: A user claimed scan results - mission.assigned: A mission was assigned to a user - mission.completed: A mission was marked complete - mission.rated: A C2 rating was submitted for a mission - enrollment.created: A user enrolled in an Institute course - enrollment.completed: A user completed a course - certification.earned: A user earned a certification - payout.finalized: A quarterly payout was finalized - alert.breach: A Shield breach alert was triggered

Payload format:

Every webhook delivery includes: { "event": "scan.completed", "timestamp": "2026-03-22T12:00:00Z", "data": { "scan_id": "uuid", "target_domain": "example.com", "score_overall": 72, "status": "completed" } }

Signature verification:

Every delivery includes an X-CDA-Signature header. Verify it by computing an HMAC-SHA256 of the raw request body using your endpoint's secret:

const crypto = require("crypto"); const signature = crypto .createHmac("sha256", endpointSecret) .update(rawBody) .digest("hex"); const isValid = signature === requestHeaders["x-cda-signature"];

Always verify signatures before processing payloads. Reject requests with invalid or missing signatures.

Retry policy:

Failed deliveries (non-2xx responses or timeouts) are retried with exponential backoff: - Attempt 1: Immediate - Attempt 2: 1 minute - Attempt 3: 5 minutes - Attempt 4: 30 minutes - Attempt 5: 2 hours (final attempt)

After 5 failed attempts, the delivery is moved to the dead letter queue. You can inspect failed deliveries via:

GET /rest/v1/webhook_deliveries?endpoint_id=eq.{id}&status=eq.failed

Delivery fields: id, endpoint_id, event_type, payload, status, response_status, attempts, next_retry_at, created_at.

Best practices: - Respond with 200 within 10 seconds. Process payloads asynchronously. - Use the event timestamp for ordering, not delivery time. - Implement idempotency. The same event may be delivered more than once. - Store the endpoint secret securely. Rotate it if compromised. - Monitor the webhook_deliveries table for persistent failures.

CDA.Help | Documentation