DocumentationAPI v1
Documentation/Guides/Webhooks

Webhooks

Results, fixtures and live scores posted to your server the moment they happen.

4events Live, Complete, Analyst Signed 5endpoints a plan

Instead of polling, give us an https address. When a result lands, a fixture is added, a match goes in play or its score moves, we post one JSON event to it, signed with your secret. If your side is down we retry for about 3 hours, and every attempt is in your dashboard for 30 days.

1

Add your address

In the dashboard, under Webhooks: an https address that answers from the public internet, and the events you want.

Add an endpoint
2

Watch the test arrive

Saving sends a webhook.test event at once, so you see the request your code will get and the answer it gave.

3

Check it, answer 2xx

Check the signature with your secret, answer any 2xx within 10 seconds, then do the work. That is all a delivery needs.

The events

Choose any of them for each endpoint
EventWhen it is sentIn data
match.finishedA result lands in the archive, with the score, both averages and the match id. Only for matches played in the last two days, so filling in old seasons never sends anything.match_idplayed_oneventevent_idroundwinnerloser
fixture.addedA new match appears on the draw, with its date and time where known.fixture_ideventrounddatestarts_atdate_confirmedplayers
match.startedA match goes in play on the live feed.fixture_ideventrounddatestarts_atdate_confirmedplayers
score.changedThe score moves on the live feed, checked every five minutes. One event can cover several legs.fixture_ideventrounddatestarts_atdate_confirmedplayersprevious_score
webhook.testSent when you add an endpoint or press Send test. Sent once, never retried.message

What arrives

Real events, as they were posted
{
    "id": "evt_UPDynB7GYCnG",
    "type": "match.finished",
    "created": "2026-11-04T06:08:02Z",
    "data": {
        "match_id": "mt_1PJWAT7H6VZ1WHG5C6KJ",
        "played_on": "2026-11-02",
        "event": "China Championship",
        "event_id": "ev_R18E03",
        "round": "round_robin",
        "winner": {
            "player_id": "pl_Z4W4X1",
            "name": "Zhuolun Luo",
            "score": 5,
            "average": 69.38
        },
        "loser": {
            "player_id": "pl_3R65AZ",
            "name": "Chao Geng",
            "score": 4,
            "average": 66.75
        }
    }
}
Ids are the API's own: look a match_id or player_id up on the endpoints
UTC throughout

The request

POST, Content-Type application/json
HeaderExampleWhat it is
X-DartsAPI-Eventmatch.finishedThe event's type, the same as type in the body.
X-DartsAPI-Deliverydlv_Hq3mT8wKc2RzThis delivery. The same on every retry of it, and shown against it in your dashboard.
X-DartsAPI-Signaturet=1794552990,v1=5f1c...e9a2When it was signed, and the HMAC-SHA256 of the time, a dot and the raw body.
User-AgentDartsAPI-Webhooks/1.0For your logs and your firewall.

Check a delivery is ours

Your signing secret

Your secret starts whsec_ and is the same for every endpoint on your account. Sign the time, a dot and the raw body with it, compare in constant time, and refuse anything older than five minutes so a captured request cannot be replayed.

// your endpoint, e.g. /webhooks/darts
$secret = getenv('DARTSAPI_WEBHOOK_SECRET');
$body   = file_get_contents('php://input');          // the raw body, before any decoding
parse_str(strtr($_SERVER['HTTP_X_DARTSAPI_SIGNATURE'] ?? '', ',', '&'), $sig);
$want   = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, $secret);

if (!hash_equals($want, $sig['v1'] ?? '') || abs(time() - (int) ($sig['t'] ?? 0)) > 300) {
    http_response_code(400);                       // not from us, or too old
    exit;
}
http_response_code(200);                           // answer first, work after
$event = json_decode($body, true);

Delivery and retries

Every attempt logged for 30 days
1At oncethe first try
2+1 minretry 1
3+5 minretry 2
4+15 minretry 3
5+30 minretry 4
6+60 minretry 5
7+90 minretry 6
!Failedafter about 3 hours

What counts as delivered

Any 2xx within 10 seconds. A redirect is not followed and counts as a miss, so give the final address.

Which addresses

https only, and never one that resolves to a private or reserved network. The address we checked is the one we post to.

From your dashboard

Retry a delivery now, see its payload and the answer you gave, pause or resume an endpoint, or send a test.

Good practice

Answer, then work

Reply 2xx as soon as the signature checks, and do the slow work afterwards, so a busy moment never looks like an outage.

Expect a repeat

A retry sends the same event again. Keep the event id you have handled and skip one you have seen.

Order by time, not arrival

A retried event can land after a newer one. Use created, and the match's own score, to decide what is latest.

Webhooks come with Live, Complete and Analyst

The same key, up to 5 endpoints and every event, from $39 a month.