> ## Documentation Index
> Fetch the complete documentation index at: https://playwave.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# PATCH /session/heartbeat

> Send a heartbeat to keep the game session alive

<Badge color="blue">PATCH</Badge> `/v1/game/session/heartbeat`

Reports that the game session is still active. Must be called every **2 minutes (120 seconds)**.

If heartbeats stop, the session is automatically deleted when the <Tooltip tip="Time-To-Live — Redis automatically deletes the session key after this duration (4 minutes for game sessions)">Redis TTL</Tooltip> (4 min) expires.

## Request

### Headers

<ParamField header="X-Api-Key" type="string" required>
  Per-game API Key.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  `application/json`
</ParamField>

### Body

<ParamField body="game_session_id" type="string" required>
  Game session ID returned by the verify endpoint.
</ParamField>

<ParamField body="provider_user_id" type="string" required>
  Roblox Player UserId (string).
</ParamField>

```json theme={null}
{
  "game_session_id": "gs_550e8400-e29b-41d4-a716-446655440000",
  "provider_user_id": "123456789"
}
```

## Response

### Success — <Badge color="green">200</Badge>

```json theme={null}
{
  "success": true,
  "request_id": "req_abc123",
  "data": {
    "result": "OK",
    "next_heartbeat_in": 120,
    "play_duration_sec": 1800
  }
}
```

<ResponseField name="result" type="string" required>
  Session status. One of `OK`, `CHARGE_EXHAUSTED`, or `SESSION_REPLACED`.
</ResponseField>

<ResponseField name="next_heartbeat_in" type="integer" required>
  Recommended wait time until next heartbeat (seconds). Default `120`.
</ResponseField>

<ResponseField name="play_duration_sec" type="integer" required>
  Server-side cumulative play time (seconds).
</ResponseField>

### result values

| result             | Description                                                                                  | Action                                                                               |
| ------------------ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `OK`               | Normal                                                                                       | Send next heartbeat after `next_heartbeat_in`                                        |
| `CHARGE_EXHAUSTED` | <Tooltip tip="Virtual currency used for PC cafe play-time billing">G-coin</Tooltip> depleted | Notify user. Server auto-terminates after 2 min grace                                |
| `SESSION_REPLACED` | Another game session started on the same PC                                                  | Stop heartbeat. Do not call session end — the server already terminated this session |

<Warning>
  **Launcher heartbeat timeout**: The server also checks the launcher's heartbeat when processing game server heartbeats. If the launcher hasn't sent a heartbeat for over **4 minutes**, the server considers the launcher dead and terminates the game session. In this case, the heartbeat returns HTTP `410 SESSION_ENDED`. This can happen when the PlayWave launcher crashes or is force-closed while the game is still running.
</Warning>

### Errors

| HTTP                              | Code                | Description                         | Action                    |
| --------------------------------- | ------------------- | ----------------------------------- | ------------------------- |
| <Badge color="yellow">404</Badge> | `SESSION_NOT_FOUND` | Session not found (expired/deleted) | Stop heartbeat, kick user |
| <Badge color="orange">410</Badge> | `SESSION_ENDED`     | Session already ended               | Stop heartbeat, kick user |
| <Badge color="orange">410</Badge> | `SESSION_ENDING`    | Termination in progress             | Stop heartbeat, kick user |

## Luau example

```lua theme={null}
local function sendHeartbeat(gameSessionId, providerUserId)
    local success, response = pcall(function()
        return HttpService:RequestAsync({
            Url = API_URL .. "/game/session/heartbeat",
            Method = "PATCH",
            Headers = {
                ["X-Api-Key"] = API_KEY,
                ["Content-Type"] = "application/json",
            },
            Body = HttpService:JSONEncode({
                game_session_id = gameSessionId,
                provider_user_id = providerUserId,
            }),
        })
    end)

    if not success then
        warn("[Playwave] Heartbeat failed:", response)
        return nil, "NETWORK_ERROR"
    end

    if response.StatusCode == 404 or response.StatusCode == 410 then
        return nil, "SESSION_LOST"
    end

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