How do I get UFC fight results the moment a fight ends?
Register an HTTPS endpoint for the fight.result webhook (Pro and Business plans) and the MMA Fight Data API posts each result to your server within minutes of the official decision: winner, method, round and time. Every delivery is signed with your endpoint's secret. On other plans, poll the card once a minute while it is live.
Free plan: 1,000 requests a month, no card. Paid plans from $19/mo.
Endpoints used
| Method & path | What it returns | Plan |
|---|---|---|
POST /v1/webhook-endpoints | Signed webhooks for fight results, card changes, event completion and consensus odds moves · no polling needed. | Pro and up |
GET /v1/events?org=ufc&status=live | MMA schedule + results API: full fight cards, venues, broadcasts, and a card-change diff log for late reshuffles. | All plans |
GET /v1/events/{slug} | One event with its full fight card (both corners, results when completed), venue and event-level broadcasts. | All plans |
GET /v1/fights/{id} | One bout with corners, result (winner, method, round, time), referee and bonus flags. | All plans |
The code
curl registers the endpoint. The Python block is a Flask receiver that checks the signature and stores each result; the JavaScript block is the polling fallback, fast on fight night and slow the rest of the week.
Every block runs as shown with your API key in the UFCAL_API_KEY environment variable. Python uses pip install ufcalendar and JavaScript or TypeScript uses npm install @ufcalendar/sdk; any HTTP client works too.
curl -X POST "https://api.ufcalendar.com/v1/webhook-endpoints" \
-H "Authorization: Bearer $UFCAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/ufcalendar", "events": ["fight.result", "card.changed"]}'
# The response returns the endpoint id and its signing secret (whsec_...), once.
# Store the secret: every delivery is signed with it.# pip install flask
import hashlib
import hmac
import os
import time
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["UFCAL_WEBHOOK_SECRET"] # the whsec_... value returned once at registration
results = {} # fight_id -> latest delivery; use your database in production
def signature_ok(raw: bytes, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
t, v1 = parts.get("t", ""), parts.get("v1", "")
expected = hmac.new(SECRET.encode(), t.encode() + b"." + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1) and abs(time.time() - int(t)) <= 300
@app.post("/hooks/ufcalendar")
def ufcalendar_hook():
raw = request.get_data() # verify the raw bytes, before any JSON parsing
if not signature_ok(raw, request.headers.get("X-UFCalendar-Signature", "")):
abort(400)
delivery = request.get_json()
if delivery["type"] == "fight.result":
d = delivery["data"]
# Upsert on fight_id: a correction arrives as a higher revision with "corrected": true
if d["revision"] >= results.get(d["fight_id"], {}).get("revision", 0):
results[d["fight_id"]] = d
r = d["result"]
print(f"{d['fighters']['a']} vs {d['fighters']['b']}: {r['winner'] or 'no winner'}, {r['method']}, R{r['round']} {r['time']}")
return "", 204// npm install @ufcalendar/sdk
// Polling fallback for plans without webhooks: fast while a card is live, slow otherwise.
import { FightAPI } from "@ufcalendar/sdk";
const api = new FightAPI(); // reads UFCAL_API_KEY from the environment
const watching = new Set(); // event slugs seen live
const announced = new Set(); // fight ids already handled
async function poll() {
for await (const ev of api.events({ org: "ufc", status: "live" })) watching.add(ev.slug);
for (const slug of watching) {
const event = await api.event(slug);
for (const f of event.card) {
if (!f.result || announced.has(f.id)) continue;
announced.add(f.id);
const winner = [f.fighter_a, f.fighter_b].find((c) => c?.id === f.result.winner_fighter_id);
console.log(`${f.fighter_a.name} vs ${f.fighter_b.name}: ${winner?.name ?? "no winner"}, ${f.result.method}, R${f.result.round} ${f.result.time}`);
}
// One last pass after the card flips to completed, so the main event is never missed
if (event.status !== "live") watching.delete(slug);
}
setTimeout(poll, watching.size ? 60_000 : 15 * 60_000);
}
poll();Sample response
The documented payload, filled with real values from the same bout. … marks a value left out.
{
"id": "res#83680",
"type": "fight.result",
"data": {
"fight_id": 83680,
"event": {
"id": 2530,
"slug": "ufc-333-2026-10-24",
"title": "UFC 333: Volkanovski vs Evloev",
"org": "ufc"
},
"fighters": { "a": "Petr Yan", "b": "Merab Dvalishvili" },
"fighter_a": { "id": 360, "name": "Petr Yan" },
"fighter_b": { "id": 194, "name": "Merab Dvalishvili" },
"result": {
"winner": "Petr Yan",
"winner_fighter_id": 360,
"method": "Decision - Unanimous",
"round": 5,
"time": "5:00"
},
"revision": 1,
"corrected": false
},
"sent_at": …
}Notes and pitfalls
- Verify before you trust. The X-UFCalendar-Signature header carries t= with a Unix timestamp and v1= with an HMAC-SHA256, keyed with your whsec_ secret, of the timestamp, a dot and the raw request body. Reject anything older than five minutes.
- A corrected result arrives as a new delivery with a higher revision, corrected set to true and the previous result attached. Upsert on fight_id, and deduplicate on the delivery id.
- Webhooks are for Pro and Business plans; a lower plan gets 403 tier_required. Each account can keep three active endpoints. Subscribe the same endpoint to card.changed and event.completed to keep cards in sync.
- Poll once a minute only while a card is live: one listing call plus one card call per live event. Fetch each card one more time after it flips to completed, or the main event's result can slip past.
Related pages
More code guides
UFCalendar is an independent publication, not affiliated with UFC/Zuffa/TKO, PFL, OKTAGON MMA, BKFC, RIZIN FF or any promotion. Organization names are used to describe the data. Terms of service · Refund Policy