Receive events: webhook system
Event data flow
When something happens in Minecraft (player dies, login, etc.), the game plugin sends an HTTP POST request to your plugin. That's called a Webhook.
Flow:
Minecraft Event (player_death)
↓
Minecraft plugin checks configServerAPI.yml
↓
Sends HTTP POST → http://localhost:PORT/webhook
↓
Your plugin (@app.route("/webhook")) receives
↓
Your plugin processes & responds
Available events
[!NOTE] A complete list of all events can be found in the
configServerAPI.ymlin the project. Here are a few examples:
player_deathplayer_respawnplayer_joinplayer_quitblock_breakentity_death
Webhook implementation: 3 steps
1. Start Flask server (in the main thread)
from flask import Flask, request
app = Flask(__name__)
def start_server():
app.run(host="127.0.0.1", port=8001, debug=False, threaded=True)
import threading
srv = threading.Thread(target=start_server, daemon=True)
srv.start()
2. Define /webhook endpoint
@app.route("/webhook", methods=['POST'])
def webhook():
try:
data = request.json
event_type = data.get("event")
if event_type == "player_death":
print(f"Player died: {data.get('player')}")
elif event_type == "block_break":
print(f"Block broken: {data.get('block')}")
return {"status": "ok"}, 200
except Exception as e:
print(f"Webhook error: {e}")
return {"status": "error"}, 500
3. Register in config.yaml
MyPlugin:
Enable: true
WebServerPort: 8001
and in the configServerAPI.yml:
urls:
- "http://localhost:7777/webhook"
- "http://localhost:7878/webhook"
- "http://localhost:7979/webhook"
- "http://localhost:8080/webhook"
- "http://localhost:8001/webhook" # Your webhook
Complete example: DeathCounter
from flask import Flask, request
import json
from pathlib import Path
app = Flask(__name__)
DATA_DIR = Path(".") / "data"
DEATHS_FILE = DATA_DIR / "deathcount.json"
# Load counter
if DEATHS_FILE.exists():
with open(DEATHS_FILE) as f:
death_count = json.load(f).get("count", 0)
else:
death_count = 0
@app.route("/")
def index():
return f"<h1>Death counter: {death_count}</h1>"
@app.route("/webhook", methods=['POST'])
def webhook():
global death_count
data = request.json
if data.get("event") == "player_death":
death_count += 1
# Save
with open(DEATHS_FILE, "w") as f:
json.dump({"count": death_count}, f)
print(f"[+] Deaths: {death_count}")
return {"status": "ok"}, 200
if __name__ == '__main__':
import threading
threading.Thread(target=lambda: app.run(port=8001), daemon=True).start()
input("Server is running. Enter to stop...")
Integrate webhook into your plugin
To receive webhooks, you need an HTTP server in your plugin. Flask is perfect for this:
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
event_data = request.json
event_type = event_data.get("event")
if event_type == "player_death":
print("Player has died!")
elif event_type == "player_respawn":
print("Player has respawned!")
return "OK", 200
That is the minimum. Your plugin must:
- Start Flask and listen on port X
- Provide the
/webhookendpoint - Process the POST request and react
Practical example: The timer
The timer plugin reacts to two events:
@app.route('/webhook', methods=['POST'])
def webhook():
ev = request.json.get("event")
if ev == "player_death":
window.evaluate_js("resetTimer(); setPaused(true);")
elif ev == "player_respawn":
window.evaluate_js("setPaused(false);")
return "OK"
If a player dies:
- Timer is reset
- Timer is paused
When a player respawns:
- Timer continues running
Understanding the event payload
When an event arrives, the request looks like this:
{
"load_type": "INGAME_GAMEPLAY",
"event": "player_death",
"message": "Player died from fall damage"
}
Depending on load_type you can program different behaviors:
INGAME_GAMEPLAY: The event is from the current gameSTARTUP: The event is at server startup- Others: see
configServerAPI.yml
Configure webhook URL
Your plugin must have a port setting in the config.yaml. The configServerAPI.yml then calls this URL:
# config.yaml
MinecraftServerAPI:
WebServerPortDeathCounter: 7979
WebServerPortTimer: 7878
The webhook URLs are then configured like this:
# configServerAPI.yml
webhooks:
urls:
- "http://localhost:7979/webhook" # DeathCounter
- "http://localhost:7878/webhook" # Timer
[!IMPORTANT] The port must be unique! No other plugin is allowed to use the same port.
Threading: Start Flask in the background
An important point: Your plugin continues to run after registration. If you call Flask's app.run() directly, it blocks everything afterwards.
The solution: Start Flask in a thread:
import threading
def start_flask_server():
app.run(host='127.0.0.1', port=7878, debug=False)
# In the main program:
flask_thread = threading.Thread(target=start_flask_server, daemon=True)
flask_thread.start()
# The rest of your code continues to run in parallel...
With daemon=True the thread will automatically end when your plugin exits.
Error handling
Everything doesn't always work. A few important points:
-
Webhook not working?
- Check that your port is set in
config.yaml - Check that the URL in
configServerAPI.ymlis correct - Look in your log file
- Check that your port is set in
-
Port already in use?
- Choose a different port or exit the other program
Summary
- Webhooks are HTTP POST requests from Minecraft to your plugin
- Flask makes implementation easy
- The
/webhookendpoint processes incoming events - The port must be synchronized in
config.yamlandconfigServerAPI.yml - Threading is important so that the Flask server doesn't block everything
Next chapter: Configuration and data storage