Callbacks reference

Every webhook payload, field by field. All callbacks POST to your webhook, carry a deterministic eventId, and retry until acknowledged — semantics in the Callbacks & reliability chapter.

Common fields & retry tiers

FieldTypeDescription
eventIdstringunique, deterministic id — dedupe on it (e.g. match_aborted:<matchId>)
typestringmatch_found · match_aborted · game_result · tournament_complete · tournament_round_started · fairplay_flag
TierBehaviour
game_result · tournament_completeretry forever (die only on a permanent 4xx ≠ 409)
all other events20 attempts, then dead-letter (alerted on the engine side, never silent)
backoff[1, 2, 5, 15, 30, 60] seconds, last value repeats
delivered2xx or 409 (idempotent duplicate)

match_found

FieldTypeDescription
matchIdstringmatch id (== the socket room id)
variant / timeControlstringgame type
tournamentIdstring | nullset when the match comes from a tournament
timeControlDetailobject{ base, inc } in seconds
players[]array{ userId, username, color } per player — the engine pushes the game to both sockets itself
jsonc
{
  "eventId": "evt_001",
  "type": "match_found",
  "matchId": "match_abc",
  "variant": "standard",
  "timeControl": "blitz_3_2",
  "tournamentId": null,
  "timeControlDetail": { "base": 180, "inc": 2 },
  "players": [
    { "userId": "user_123", "username": "magnus_fan", "color": "white" },
    { "userId": "user_456", "username": "the_turk", "color": "black" }
  ]
}
// A notification, not a mechanism: the engine already pushed the game start to
// both sockets. Use it for bookkeeping — or to notify an OFFLINE tournament
// entrant whose round just opened (the one case it does real work).
// Direct play_bot games fire NO match_found callback; the autoBot fallback
// fires one with a single (human) player.
// Every seat carries username (omitted for a bot seat / unknown) — uniform with
// the WS seats and the other callbacks. You may ignore it; you own the canonical name.

game_result

FieldTypeDescription
matchIdstringmatch id
tournamentIdstring | nullset when from a tournament
variantstringrating category — file the rating from this, never from a remembered match_found
timeControlstringtime-control id (e.g. blitz_3_2); EMPTY for a custom control
speedstringspeed bucket the rating is keyed on (e.g. blitz); EMPTY for a custom control — record the game, do not rate it
resultstringwhite_win | black_win | draw
terminationstringcheckmate | resign | timeout | stalemate | insufficient | threefold | fivefold | fifty_move | seventy_five | draw_agreement
startedAt / endedAtstring (Y-m-d H:i:s.v)game time span
movesUci[]string[]full move list in UCI
players[].userId / username / colorstringseat (username omitted for a bot seat / unknown)
players[].scorenumber1 · 0.5 · 0
players[].ratingBeforeobjectechoes the triple you supplied (may include lastGameAt / floor). OMITTED for unrated games
players[].ratingAfterobjectcomputed { r, rd, vol } — persist this. OMITTED for unrated games
players[].deltanumberratingAfter.r − ratingBefore.r. OMITTED for unrated games
jsonc
{
  "eventId": "evt_003",
  "type": "game_result",
  "matchId": "match_abc",
  "tournamentId": null,
  "variant": "standard", "timeControl": "blitz_3_2", "speed": "blitz",
  "result": "white_win",
  "termination": "checkmate",
  "startedAt": "2026-07-08 09:01:00.000",
  "endedAt":   "2026-07-08 09:07:32.000",
  "movesUci": ["e2e4","e7e5","g1f3"],
  "players": [
    { "userId": "user_123", "username": "magnus_fan", "color": "white", "score": 1,
      "ratingBefore": { "r": 1512.3, "rd": 84.1, "vol": 0.0598, "lastGameAt": 1719828000000, "floor": 1400 },
      "ratingAfter":  { "r": 1524.7, "rd": 78.2, "vol": 0.0597 }, "delta": 12.4 },
    { "userId": "user_456", "username": "the_turk", "color": "black", "score": 0,
      "ratingBefore": { "r": 1600.0, "rd": 60.0, "vol": 0.0600 },
      "ratingAfter":  { "r": 1589.1, "rd": 58.4, "vol": 0.0601 }, "delta": -10.9 }
  ]
}

// Unrated (rated:false or any bot game) → identical payload; ONLY the
// ratingBefore/After/delta block is omitted:
{
  "eventId": "evt_010",
  "type": "game_result",
  "matchId": "match_790",
  "tournamentId": null,
  "variant": "standard", "timeControl": "blitz_3_2", "speed": "blitz",
  "result": "draw",
  "termination": "stalemate",
  "startedAt": "2026-07-08 10:15:00.000",
  "endedAt":   "2026-07-08 10:21:44.000",
  "movesUci": ["e2e4","e7e5","g1f3"],
  "players": [
    { "userId": "user_123", "username": "magnus_fan", "color": "white", "score": 0.5 },
    { "userId": "user_456", "username": "the_turk", "color": "black", "score": 0.5 }
  ]
}

match_aborted

FieldTypeDescription
matchIdstringaborted match id
tournamentIdstring | nullset when from a tournament
reasonstringno_show | aborted — no_show means the gate was never confirmed, not a failure to connect
players[]array{ userId, username, color } — the game never started, so there is no result and no rating change
jsonc
{
  "eventId": "match_aborted:match_abc",
  "type": "match_aborted",
  "matchId": "match_abc",
  "tournamentId": null,
  "reason": "no_show",
  "players": [
    { "userId": "user_123", "username": "magnus_fan", "color": "white" },
    { "userId": "user_456", "username": "the_turk", "color": "black" }
  ]
}
// Engine follow-up (already done by the time you receive this): matchmaking →
// the player who showed up is requeued; tournament → the absentee forfeits.

tournament_complete

FieldTypeDescription
tournamentIdstringtournament id
standings[]array{ userId, rank, score, games } — final standings
json
{
  "eventId": "evt_004",
  "type": "tournament_complete",
  "tournamentId": "trn_44",
  "standings": [
    { "userId": "user_456", "rank": 1, "score": 21, "games": 14 },
    { "userId": "user_123", "rank": 2, "score": 19, "games": 14 }
  ]
}

tournament_round_started

jsonc
{ "eventId": "evt_005", "type": "tournament_round_started", "tournamentId": "trn_44", "round": 3 }
// Optional UI hint — safe to ignore.

fairplay_flag

FieldTypeDescription
flagTypestringrepeat_pairing | sandbag_pattern | lopsided_pair | engine_assist
userIds[]string[]flagged users
matchIds[]string[]matches the evidence covers
evidenceobjectfree-form, heuristic-specific detail
jsonc
{
  "eventId": "fairplay_flag:engine_assist:match_abc",
  "type": "fairplay_flag",
  "flagType": "engine_assist",
  "userIds": ["user_123"],
  "matchIds": ["match_abc"],
  "evidence": { "engineMatchRate": 0.97, "pliesAnalyzed": 58 }
}
// The engine only signals; the platform correlates identity and enforces.