Webhooks
CTFFactory webhooks allow your systems to receive real-time notifications when important events occur on the platform. Instead of polling the API repeatedly, you register an HTTPS endpoint and CTFFactory sends a JSON POST request to it whenever a subscribed event fires.
Registering a Webhook Endpoint
- Navigate to Workspace Settings > Integrations > Webhooks.
- Click Add Endpoint.
- Enter your endpoint URL (must be HTTPS; HTTP endpoints are rejected).
- Select the events you want to receive (see the Event Types table below).
- Click Save. CTFFactory generates a signing secret and displays it once β copy it immediately and store it securely.
You can also register endpoints via the API:
POST /api/v1/webhooks
Authorization: Bearer ctff_...
Content-Type: application/json
{
"url": "https://your-app.example.com/hooks/ctffactory",
"events": ["ctf.deployed", "challenge.generated"],
"description": "Production event handler"
}
Event Types
| Event | Trigger |
|---|---|
ctf.deployed |
A CTF instance has been successfully deployed and is accepting traffic |
ctf.stopped |
A CTF instance has been stopped and its infrastructure torn down |
ctf.expired |
A CTF instance has passed its scheduled end time and been automatically shut down |
challenge.generated |
An AI-generated challenge has been created (from the organizer flow or a learning card lab) |
More event types may be added over time. Subscribe only to the events relevant to your integration.
Payload Structure
Each webhook delivery is a JSON POST request to your endpoint. All events share a common envelope structure:
{
"event": "ctf.deployed",
"id": "evt_01JXABCDE12345",
"created_at": "2026-05-18T14:32:00Z",
"workspace_id": "ws_01JXABC123",
"data": { }
}
The data field contains event-specific payload. Examples:
ctf.deployed
{
"data": {
"ctf_id": "ctf_01JXABC456",
"name": "Spring Boot Camp CTF",
"url": "https://ctf.yourcompany.com/spring-boot-camp/",
"deployed_at": "2026-05-18T14:32:00Z",
"deployed_by": "user_01JXABC789"
}
}
challenge.generated
{
"data": {
"ctf_id": "ctf_01JXABC456",
"challenge_id": "chal_01JXABC999",
"title": "Token Forge",
"category": "web",
"difficulty": 7,
"deployable": true,
"generated_by": "user_01JXABC789"
}
}
Verifying the HMAC-SHA256 Signature
Every webhook delivery includes an X-CTFFactory-Signature header containing an HMAC-SHA256 digest of the raw request body, signed with the endpoint's signing secret. Always verify this signature before processing the payload to ensure the request genuinely originated from CTFFactory and has not been tampered with.
The signature format is:
X-CTFFactory-Signature: sha256=<hex_digest>
Python Verification Example
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "your_signing_secret_here" # Retrieved when you registered the endpoint
@app.route("/hooks/ctffactory", methods=["POST"])
def handle_webhook():
signature_header = request.headers.get("X-CTFFactory-Signature", "")
if not signature_header.startswith("sha256="):
abort(400, "Missing or malformed signature header")
received_sig = signature_header[len("sha256="):]
# Compute the expected signature using the raw request body
expected_sig = hmac.new(
key=WEBHOOK_SECRET.encode("utf-8"),
msg=request.get_data(), # Raw bytes, not parsed JSON
digestmod=hashlib.sha256
).hexdigest()
# Use hmac.compare_digest to prevent timing attacks
if not hmac.compare_digest(expected_sig, received_sig):
abort(401, "Signature verification failed")
event = request.json
print(f"Received event: {event['event']} (id={event['id']})")
# Handle specific events
if event["event"] == "ctf.deployed":
handle_ctf_deployed(event["data"])
return "", 200 # Acknowledge receipt
def handle_ctf_deployed(data):
print(f"CTF deployed: {data['name']} at {data['url']}")
Important: Always compute the HMAC over the raw request body bytes, not over a re-serialized version of the parsed JSON. JSON serialization is not deterministic and will produce a different digest.
Delivery and Retries
CTFFactory expects your endpoint to return a 2xx HTTP status code within 10 seconds to acknowledge receipt. If the endpoint times out or returns a non-2xx response, CTFFactory retries the delivery with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
After 4 failed retries, the delivery is marked as failed and no further attempts are made. Failed deliveries are visible in the Webhook Delivery Log under Workspace Settings. You can manually replay any delivery from the log.
Testing Your Endpoint
Use the Send Test Event button in the Webhooks settings page, or the API endpoint POST /api/v1/webhooks/{webhook_id}/test, to send a synthetic event payload to your endpoint. This is useful for verifying your signature verification code before relying on it in production.