All systems normalData updated just nowStatusChangelogSupport
badmintonapi

Webhooks

On the Live plan we send a signed POST to your own address the moment something happens, so you never have to poll. Add up to 10 endpoints in your account, pick the events each one wants, copy its signing secret, and press Test to receive a real recent match at once.

Events

match.started
The first rally of a match is played.
match.finished
A match ends: won, retired or walkover, with the score.
result.corrected
A finished match's score or winner is changed afterwards.

What we send

A JSON body: the event's own id (the same for every endpoint and every try, so you can drop a repeat), its type, when it was created, and data, the match in the same shape as /v1/events/{id}. A test delivery also carries "test": true.

A delivery
POST /your/webhook HTTP/1.1
Content-Type: application/json
X-BDA-Event: match.finished
X-BDA-Delivery: evt_86dd39b266bb3d9d0ca2
X-BDA-Signature: t=1790719740,v1=5f2a...

{
  "id": "evt_86dd39b266bb3d9d0ca2",
  "type": "match.finished",
  "created": "2026-10-09T13:41:07Z",
  "data": { "id": "ev_q63vfven", "status": "finished", "winner": 1 }
}

Check it came from us

X-BDA-Signature is t=<unix seconds>,v1=<hex>, where v1 is an HMAC-SHA256 of t, a dot and the raw body, keyed with the endpoint's secret. Check it against the raw bytes before parsing, compare in constant time, and refuse anything more than five minutes old so an old delivery cannot be replayed.

PHP
$secret = getenv('BDA_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_BDA_SIGNATURE'] ?? ''), $p);
$good = isset($p['t'], $p['v1'])
    && hash_equals(hash_hmac('sha256', $p['t'] . '.' . $body, $secret), $p['v1'])
    && time() - (int) $p['t'] < 300;
if (!$good) { http_response_code(400); exit; }
http_response_code(200);   // answer first, work after
$event = json_decode($body, true);
Python (Flask)
import hmac, hashlib, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["BDA_WEBHOOK_SECRET"].encode()

@app.post("/webhook")
def hook():
    raw = request.get_data()
    parts = dict(p.split("=", 1) for p in request.headers["X-BDA-Signature"].split(","))
    want = hmac.new(SECRET, parts["t"].encode() + b"." + raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(want, parts["v1"]) or time.time() - int(parts["t"]) > 300:
        abort(400)
    queue_for_later(request.get_json())
    return "", 200

Answering, and tries

Answer any 2xx within 10 seconds and do the work afterwards. Anything else is tried again, 8 tries in all over about 3 days (5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours, then a day apart). A 4xx other than 408 or 429 ends that delivery at once, since trying again would give the same answer. After 20 failed tries in a row the endpoint is paused and you are emailed; resume it in your account when your server is back. Addresses must be https on port 443 and public: private and local addresses are refused.

Testing before a match is on

You do not need to wait for a live match to build your receiver. Press Test beside an endpoint in your account and it receives a real recent match, signed like any other delivery, with "test": true in the body. Keep the event id of every delivery you have handled, and skip any you have seen: a delivery that timed out on your side may arrive again on its next try.