Skip to main content
DELETE /v1/game/session/end Call when a player leaves the game. Stops billing and cleans up the session.

Request

Headers

X-Api-Key
string
required
Per-game API Key.
Content-Type
string
required
application/json

Body

game_session_id
string
required
Game session ID to terminate.
play_duration_sec
integer
Client-measured play time (seconds). Optional — for comparison with the server-side value.
{
  "game_session_id": "gs_550e8400-e29b-41d4-a716-446655440000",
  "play_duration_sec": 1795
}

Response

Success — 200

{
  "success": true,
  "request_id": "req_abc123",
  "data": {
    "result": "SUCCESS",
    "play_duration_sec": 1800,
    "client_play_duration_sec": 1795
  }
}
result
string
required
Always "SUCCESS".
play_duration_sec
integer
required
Server-side play time (seconds) — this is the authoritative value used for settlement.
client_play_duration_sec
integer | null
The play_duration_sec value sent in the request. null if not provided.

Errors

HTTPCodeDescriptionAction
404SESSION_NOT_FOUNDSession not found (already expired/deleted)Ignore
409ALREADY_ENDEDSession already endedIgnore
404 and 409 errors mean the session is already cleaned up — safe to ignore. Just log and continue normal flow.

Luau example

local function endSession(gameSessionId, playDurationSec)
    local requestBody = {
        game_session_id = gameSessionId,
    }

    if playDurationSec then
        requestBody.play_duration_sec = playDurationSec
    end

    local success, response = pcall(function()
        return HttpService:RequestAsync({
            Url = API_URL .. "/game/session/end",
            Method = "DELETE",
            Headers = {
                ["X-Api-Key"] = API_KEY,
                ["Content-Type"] = "application/json",
            },
            Body = HttpService:JSONEncode(requestBody),
        })
    end)

    if not success then
        warn("[Playwave] End session failed:", response)
        return
    end

    -- 404/409 means already cleaned up — ignore
    if response.StatusCode == 404 or response.StatusCode == 409 then
        return
    end

    local body = HttpService:JSONDecode(response.Body)
    return body.data
end

When to call

PlayerRemoving

Call when an individual player leaves.
Players.PlayerRemoving:Connect(function(player)
    local session = activeSessions[player.UserId]
    if not session then return end

    activeSessions[player.UserId] = nil
    endSession(session.gameSessionId)
end)

BindToClose

Clean up all active sessions on server shutdown.
game:BindToClose(function()
    for userId, session in pairs(activeSessions) do
        activeSessions[userId] = nil
        pcall(function()
            endSession(session.gameSessionId)
        end)
    end
end)
BindToClose has a 30-second time limit. Even if HTTP calls fail, sessions are automatically cleaned up via expiration after 4 minutes.