Skip to main content

First Blood Awards using Webhooks

The previous tutorial shows you how to configure CTFd to send webhook requests to a target endpoint when a First Blood event is detected. However, it only implements half of the functionality we want to achieve.

After the endpoint receives the webhook request, the server still needs to interact with CTFd's API to award the user. The following steps will show you how to reconfigure your webhook server to extract the submission ID from the webhook event and send an API request to CTFd to generate an award.

Server Configuration


  • Flask: to run and serve the application
  • ngrok or bore: to expose the local development server publicly
  • Requests library: to make HTTP requests


  1. Make sure the Flask application is not running. Terminate it with Control + C.

  2. Install the Requests library.

    pip install requests
  3. Generate an API token and assign it to the API_TOKEN environment variable.

    export API_TOKEN="<token>"
  4. Assign your CTFd instance's URL to the BASE_URL environment variable.

    export BASE_URL="https://<subdomain>"
  5. Open the file in your code editor and add the highlighted code shown below:
    # fmt: off# pip install Flask==2.2.2# Set the WEBHOOK_SECRET envvar to your CTFd instance's webhook secretimport hashlibimport hmacimport osimport requestsfrom flask import Flask, jsonify, requestapp = Flask(__name__)@app.route('/', methods=["GET", "POST"])def verify():    if request.method == "GET":        key = os.environ["WEBHOOK_SECRET"]        token = request.args.get("token")        response =            key=key.encode(),            msg=token.encode(),            digestmod=hashlib.sha256        ).hexdigest()        return jsonify({            "response": response,        })    elif request.method == "POST":        token = os.environ["API_TOKEN"]        url = os.environ["BASE_URL"]        url = url.strip("/")        payload = request.json        s = requests.Session()        s.headers.update({"Authorization": f"Token {token}", "Content-type":"application/json"})        submission = s.get(f"{url}/api/v1/submissions/{payload['id']}", json=True).json()        r =            f"{url}/api/v1/awards",            json={                "name": "First Blood",                "value": "9000",                "category": "First Blood",                "description": f"Reward for being the first to solve {submission['data']['challenge']['name']}",                "icon":"shield",                "user_id": f"{submission['data']['user']['id']}"            },            timeout=10,        )        print(request.url)        print(request.headers)        print(request.json)        return "", 200if __name__ == "__main__":, threaded=True, host="", port=12345)

Click here for more information about interacting with CTFd's REST API.

  1. Run the Flask application.

  2. Test the endpoint by creating a new challenge and submitting a correct answer as outlined in this section.

  3. Check the user's profile to see if it received the award. To check a user's awards, follow the steps here.

In the next tutorial, we'll discuss how you can deploy the debugging applications locally, and remotely, to test CTFd's webhooks.