> ## 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

> 게임 세션 하트비트를 전송하여 세션을 유지합니다

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

게임 세션이 활성 상태임을 서버에 알립니다. **2분(120초) 간격**으로 호출해야 합니다.

하트비트를 보내지 않으면 <Tooltip tip="Time-To-Live — Redis가 이 시간(게임 세션은 4분) 후에 세션 키를 자동 삭제합니다">Redis TTL</Tooltip>(4분) 만료로 세션이 자동 삭제됩니다.

## 요청

### Headers

<ParamField header="X-Api-Key" type="string" required>
  게임별 API Key.
</ParamField>

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

### Body

<ParamField body="game_session_id" type="string" required>
  verify에서 반환된 게임 세션 ID.
</ParamField>

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

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

## 응답

### 성공 — <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>
  세션 상태. `OK`, `CHARGE_EXHAUSTED`, `SESSION_REPLACED` 중 하나.
</ResponseField>

<ResponseField name="next_heartbeat_in" type="integer" required>
  다음 하트비트까지 권장 대기 시간 (초). 기본 `120`.
</ResponseField>

<ResponseField name="play_duration_sec" type="integer" required>
  서버 기준 누적 플레이 시간 (초).
</ResponseField>

### result 값

| result             | 설명                                                           | 처리                                            |
| ------------------ | ------------------------------------------------------------ | --------------------------------------------- |
| `OK`               | 정상                                                           | `next_heartbeat_in` 후 다음 하트비트 전송              |
| `CHARGE_EXHAUSTED` | <Tooltip tip="PC방 플레이 시간 과금에 사용되는 가상 화폐">G-coin</Tooltip> 소진 | 유저에게 안내. 서버 2분 유예 후 자동 종료                     |
| `SESSION_REPLACED` | 같은 PC에서 다른 게임 세션이 시작됨                                        | 하트비트 중지. 세션 종료 API를 호출하지 마세요 — 서버에서 이미 종료 처리됨 |

<Warning>
  **런처 하트비트 타임아웃**: 게임 서버 하트비트를 처리할 때 서버는 런처의 하트비트도 함께 확인합니다. 런처가 **4분 이상** 하트비트를 보내지 않으면 서버는 런처가 죽은 것으로 판단하고 게임 세션을 종료합니다. 이 경우 하트비트 응답으로 HTTP `410 SESSION_ENDED`가 반환됩니다. PlayWave 런처가 크래시되거나 강제 종료된 경우에 발생할 수 있습니다.
</Warning>

### 에러

| HTTP                              | 코드                  | 설명            | 처리            |
| --------------------------------- | ------------------- | ------------- | ------------- |
| <Badge color="yellow">404</Badge> | `SESSION_NOT_FOUND` | 세션 없음 (만료/삭제) | 하트비트 중지, 유저 킥 |
| <Badge color="orange">410</Badge> | `SESSION_ENDED`     | 이미 종료된 세션     | 하트비트 중지, 유저 킥 |
| <Badge color="orange">410</Badge> | `SESSION_ENDING`    | 종료 진행 중       | 하트비트 중지, 유저 킥 |

## Luau 예제

```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
```
