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

# WebView 연동

> WebView를 통한 부스트/쿠폰 기능 연동

PlayCamp가 제공하는 웹 기반 UI를 통해 부스트/쿠폰 기능을 제공하는 방법을 설명합니다.

## 개요

WebView는 PlayCamp가 제공하는 부스트/쿠폰 관리 웹 UI입니다. 게임 내 임베디드 브라우저(WebView)뿐만 아니라 외부 브라우저에서도 동작합니다. 별도의 UI 개발 없이 크리에이터 부스트와 쿠폰 기능을 제공할 수 있습니다.

<Info>
  게임 내 임베디드 브라우저, 외부 브라우저(모바일/PC) 모두 지원됩니다. 게임 환경에 맞게 적합한 방식을 선택하세요.
</Info>

### Server API 직접 연동 vs WebView 비교

| 항목         | Server API 직접 연동   | WebView                 |
| ---------- | ------------------ | ----------------------- |
| **UI 개발**  | 게임사가 직접 구현 필요      | PlayCamp 제공 (추가 개발 불필요) |
| **연동 난이도** | 각 API별 개별 연동       | OTT 발급 1개 API만 연동       |
| **커스터마이징** | 완전한 자유도            | 테마 색상/폰트 커스터마이징         |
| **다국어**    | 게임사가 직접 처리         | 자동 지원 (한국어/영어)          |
| **적합한 경우** | 게임 UX에 완벽히 맞춰야 할 때 | 빠르게 기능을 제공하고 싶을 때       |

<Info>
  WebView와 Server API 직접 연동은 동시에 사용 가능합니다. 예를 들어 부스트는 WebView로, 결제 등록은 Server API로 처리할 수 있습니다.
</Info>

### 아키텍처

```mermaid theme={null}
sequenceDiagram
    participant C as 게임 클라이언트
    participant S as 게임 서버
    participant P as PlayCamp SDK API

    C->>S: 1. WebView 열기 요청
    S->>P: 2. OTT 발급 요청 (Server Key)
    P-->>S: 3. OTT 반환
    S-->>C: 4. OTT 전달
    C->>P: 5. WebView 열기 (/webview/?ott=xxx)
    C->>P: 6. 유저 인터랙션 (부스트/쿠폰 사용)
    P-->>S: 7. 웹훅으로 결과 수신
```

## 연동 흐름

<Steps>
  <Step title="OTT 발급 (게임 서버)">
    게임 서버에서 Server API Key로 OTT(One-Time Token)를 발급합니다.
  </Step>

  <Step title="WebView 열기 (게임 클라이언트)">
    게임 서버로부터 받은 OTT를 URL 파라미터에 포함하여 WebView를 엽니다.

    ```
    https://sandbox-sdk-api.playcamp.io/webview/?ott={token}
    ```
  </Step>

  <Step title="유저 인터랙션">
    유저가 WebView에서 크리에이터 부스트 등록/변경/해제, 쿠폰 검증/사용 등을 수행합니다. 별도의 게임 서버 처리가 필요 없습니다.
  </Step>

  <Step title="웹훅으로 결과 수신">
    WebView에서 발생한 이벤트(부스트 등록, 쿠폰 사용 등)가 웹훅으로 게임 서버에 전달됩니다.
  </Step>
</Steps>

## OTT 발급 API

### 요청

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST "https://sandbox-sdk-api.playcamp.io/v1/server/webview/ott" \
      -H "Authorization: Bearer ak_server_xxx:secret" \
      -H "Content-Type: application/json" \
      -d '{
        "userId": "user_12345"
      }'
    ```
  </Tab>

  <Tab title="Node SDK">
    ```typescript theme={null}
    const result = await server.webview.createOtt({
      userId: 'user_12345',
      campaignId: 'campaign_123',       // optional
      codeChallenge: 'challenge_value', // optional
      callbackId: 'cb_001',             // optional
      metadata: { key: 'value' },       // optional
    });

    console.log(result.ott);       // 일회용 토큰
    console.log(result.expiresAt); // 만료 시각 (ISO 8601)
    ```
  </Tab>

  <Tab title="Go SDK">
    ```go theme={null}
    result, err := server.Webview.CreateOTT(ctx, playcamp.WebviewOttParams{
        UserID:        "user_12345",
        CampaignID:    "campaign_123",       // optional
        CodeChallenge: "challenge_value",    // optional
        CallbackID:    "cb_001",             // optional
        Metadata:      map[string]any{"key": "value"}, // optional
    })

    fmt.Println(result.OTT)       // 일회용 토큰
    fmt.Println(result.ExpiresAt) // 만료 시각
    ```
  </Tab>
</Tabs>

### 파라미터

| 필드              | 타입     | 필수 | 설명                                                      |
| --------------- | ------ | -- | ------------------------------------------------------- |
| `userId`        | string | O  | 게임 내 유저 식별자                                             |
| `campaignId`    | string | -  | 특정 캠페인으로 WebView 범위 제한                                  |
| `codeChallenge` | string | -  | PKCE S256 challenge (Base64URL-encoded SHA256, 43-128자) |
| `callbackId`    | string | -  | 웹훅 추적용 ID                                               |
| `metadata`      | object | -  | 추가 메타데이터 (세션에 저장)                                       |

### 응답 (201 Created)

```json theme={null}
{
  "data": {
    "ott": "a1b2c3d4e5f6...64자hex",
    "expiresIn": 60
  }
}
```

| 필드          | 설명                           |
| ----------- | ---------------------------- |
| `ott`       | One-Time Token (64자 hex 문자열) |
| `expiresIn` | 만료까지 남은 시간 (초, 기본 60초)       |

<Warning>
  OTT는 **1회성**이며 **60초 TTL**입니다. 발급 후 빠르게 WebView를 열어야 합니다. 반드시 **게임 서버에서 발급**해야 하며, 클라이언트에서 직접 발급하면 Server Key가 노출됩니다.
</Warning>

## WebView URL 구성

### 기본 URL

| 환경          | URL                                                        |
| ----------- | ---------------------------------------------------------- |
| **Sandbox** | `https://sandbox-sdk-api.playcamp.io/webview/?ott={token}` |
| **Live**    | `https://sdk-api.playcamp.io/webview/?ott={token}`         |

### 옵션 파라미터

| 파라미터   | 설명               | 예시                             |
| ------ | ---------------- | ------------------------------ |
| `lang` | 언어 설정 (ko, en)   | `?ott=xxx&lang=en`             |
| `tabs` | 표시할 탭 제한 (쉼표 구분) | `?ott=xxx&tabs=sponsor,coupon` |

**tabs 옵션**

| 값           | 설명         |
| ----------- | ---------- |
| `sponsor`   | 부스트 관리 탭   |
| `coupon`    | 쿠폰 사용 탭    |
| `campaigns` | 캠페인 목록 탭   |
| `creators`  | 크리에이터 검색 탭 |

<Note>
  `tabs` 미지정 시 기본 탭이 표시됩니다 (sponsor, coupon).
</Note>

### 테마 커스터마이징

WebView의 색상과 폰트를 URL 파라미터로 커스터마이징할 수 있습니다.

| 파라미터           | 설명             | 기본값      | 예시                        |
| -------------- | -------------- | -------- | ------------------------- |
| `primaryColor` | 버튼/강조 색상 (hex) | 게임 테마 색상 | `primaryColor=FF6B35`     |
| `bgColor`      | 배경 색상 (hex)    | 다크 테마    | `bgColor=1a1a2e`          |
| `textColor`    | 텍스트 색상 (hex)   | 밝은 텍스트   | `textColor=ffffff`        |
| `fontFamily`   | 폰트 패밀리         | 시스템 기본   | `fontFamily=Noto Sans KR` |

<Note>
  hex 색상값은 `#` 없이 입력합니다. 예: `primaryColor=FF6B35`
</Note>

**전체 예시**

```
/webview/?ott=xxx&lang=ko&tabs=sponsor,coupon&primaryColor=FF6B35&bgColor=1a1a2e&textColor=ffffff
```

## 주요 기능

### 부스트 관리

WebView에서 유저가 크리에이터를 검색하고 부스트를 등록/변경/해제할 수 있습니다.

* **크리에이터 검색**: 크리에이터 키 또는 이름으로 검색 (실시간 자동완성)
* **부스트 등록**: 선택한 크리에이터에게 부스트 등록
* **부스트 변경**: 다른 크리에이터로 변경 (30일 쿨다운 적용)
* **부스트 해제**: 현재 부스트 해제

<div style={{ display: 'flex', justifyContent: 'center' }}>
  <img src="https://mintcdn.com/smartplay/jm69p60YpfOyt92m/images/webview/mobile_main_ko.png?fit=max&auto=format&n=jm69p60YpfOyt92m&q=85&s=e6a67afb4cdbad904209e0d61563332c" alt="부스트 화면 - 모바일" style={{ maxWidth: '300px', border: '1px solid #333', borderRadius: '8px' }} width="413" height="918" data-path="images/webview/mobile_main_ko.png" />
</div>

<img src="https://mintcdn.com/smartplay/jm69p60YpfOyt92m/images/webview/pc_main_ko.png?fit=max&auto=format&n=jm69p60YpfOyt92m&q=85&s=c1180cf1a83877f755a10254a583d0b5" alt="부스트 화면 - PC" style={{ border: '1px solid #333', borderRadius: '8px' }} width="1499" height="983" data-path="images/webview/pc_main_ko.png" />

**크리에이터 키 프리필**

URL에 크리에이터 키를 포함하여 검색 폼을 미리 채울 수 있습니다:

```
/webview/?ott=xxx#sponsor?creatorKey=ABC12
```

### 쿠폰 사용

2단계로 쿠폰을 사용합니다: 검증 → 사용

* **코드 입력**: 5-50자, 자동 대문자 변환, 한글 입력 시 영문 키 위치로 자동 변환
* **검증**: 유효성 확인 및 보상 아이템 미리보기
* **사용**: 확인 버튼으로 최종 사용 (사용 ID 반환)

<div style={{ display: 'flex', justifyContent: 'center' }}>
  <img src="https://mintcdn.com/smartplay/jm69p60YpfOyt92m/images/webview/mobile_main_coupon_ko.png?fit=max&auto=format&n=jm69p60YpfOyt92m&q=85&s=9c5800ffc92f8666fb1000282a22f96d" alt="쿠폰 화면 - 모바일" style={{ maxWidth: '300px', border: '1px solid #333', borderRadius: '8px' }} width="406" height="910" data-path="images/webview/mobile_main_coupon_ko.png" />
</div>

<img src="https://mintcdn.com/smartplay/jm69p60YpfOyt92m/images/webview/pc_main_coupon_ko.png?fit=max&auto=format&n=jm69p60YpfOyt92m&q=85&s=f76401bb4144e4e4be0f269b4463b4f8" alt="쿠폰 화면 - PC" style={{ border: '1px solid #333', borderRadius: '8px' }} width="1496" height="982" data-path="images/webview/pc_main_coupon_ko.png" />

## 웹훅 이벤트 연동

WebView에서 유저가 수행한 액션은 웹훅으로 게임 서버에 전달됩니다.

### WebView에서 발생하는 이벤트

| 이벤트               | 설명     | 주요 data 필드                                  |
| ----------------- | ------ | ------------------------------------------- |
| `sponsor.created` | 부스트 등록 | `userId`, `campaignId`, `creatorKey`        |
| `sponsor.ended`   | 부스트 해제 | `userId`, `campaignId`, `creatorKey`        |
| `coupon.redeemed` | 쿠폰 사용  | `couponCode`, `userId`, `usageId`, `reward` |

### 웹훅 페이로드 예시

**sponsor.created (부스트 등록)**

```json theme={null}
{
  "events": [
    {
      "event": "sponsor.created",
      "timestamp": "2024-01-15T10:30:00.000Z",
      "callbackId": "cb_001",
      "data": {
        "userId": "user_12345",
        "campaignId": "campaign_001",
        "creatorKey": "ABC12"
      }
    }
  ]
}
```

**coupon.redeemed (쿠폰 사용)**

```json theme={null}
{
  "events": [
    {
      "event": "coupon.redeemed",
      "timestamp": "2024-01-15T10:31:00.000Z",
      "callbackId": "cb_001",
      "data": {
        "couponCode": "CREATOR-ABC12-001",
        "userId": "user_12345",
        "usageId": 5678,
        "reward": [{ "itemId": "gem", "itemQuantity": 100 }]
      }
    }
  ]
}
```

<Info>
  OTT 발급 시 `callbackId`를 지정하면, 해당 세션에서 발생하는 모든 웹훅 이벤트에 지정된 ID가 포함됩니다. 웹훅 수신 및 서명 검증 방법은 [웹훅 이벤트](/ko/guides/developers/game-integration/webhook) 페이지를 참고하세요.
</Info>

## 보안

### OTT (One-Time Token)

* **1회성**: OTT는 세션 교환 시 즉시 삭제됩니다. 재사용 불가합니다
* **60초 TTL**: 발급 후 60초 내에 사용해야 합니다
* **서버 발급 필수**: Server API Key가 필요하므로 게임 서버에서만 발급 가능

### 세션 관리

* **세션 쿠키**: `httpOnly`, `SameSite=strict` 설정으로 XSS/CSRF 공격 방지
* **세션 TTL**: 10분 (마지막 API 호출 기준으로 자동 연장)
* **단일 세션**: 같은 유저는 하나의 세션만 허용. 새 세션 생성 시 기존 세션 자동 무효화

### CSRF 보호

WebView 내부의 상태 변경 요청(POST/PUT/DELETE)에는 CSRF 토큰이 자동으로 포함됩니다. 게임사에서 별도로 처리할 필요 없습니다.

### PKCE (선택)

앱 환경에서 추가 보안이 필요한 경우 PKCE(Proof Key for Code Exchange)를 사용할 수 있습니다.

```typescript theme={null}
import crypto from 'crypto';

// 1. 코드 생성 (게임 서버)
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
  .createHash('sha256')
  .update(codeVerifier)
  .digest('base64url');

// 2. OTT 발급 시 codeChallenge 포함
const { ott } = await server.webview.createOtt({
  userId: 'user_12345',
  codeChallenge,
});

// 3. WebView URL에 codeVerifier 포함
const url = `https://sandbox-sdk-api.playcamp.io/webview/?ott=${ott}&code_verifier=${codeVerifier}`;
```

## FAQ

### 세션 만료 시 어떻게 되나요?

세션이 만료되면 WebView에 재접속 안내 화면이 표시됩니다. 유저가 계속 사용하려면 게임 서버에서 새 OTT를 발급받아 WebView를 다시 열어야 합니다.

<Note>
  세션 TTL은 10분이며, API 호출 시마다 자동 연장됩니다. 단, 유휴 상태(아무 동작 없이 방치)에서는 연장되지 않습니다.
</Note>

### campaignId 스코핑이란?

OTT 발급 시 `campaignId`를 지정하면 WebView 전체가 해당 캠페인으로 범위가 제한됩니다:

* 부스트 등록/변경/해제 시 해당 캠페인의 크리에이터만 표시
* 캠페인 목록 대신 해당 캠페인 정보만 표시
* 해당 캠페인에 참여하는 크리에이터만 검색 가능

<Warning>
  존재하지 않는 `campaignId`를 지정하면 OTT 발급이 실패합니다 (404).
</Warning>

### 다국어는 어떻게 지원되나요?

* 기본: 브라우저 언어 설정에 따라 자동 감지 (한국어/영어)
* URL 파라미터 지정: `?lang=ko` 또는 `?lang=en`
* 유저가 변경한 언어 설정은 브라우저에 저장되어 다음 접속 시에도 유지됩니다

### OTT를 미리 발급해둘 수 있나요?

권장하지 않습니다. OTT는 60초 TTL이므로, 유저가 WebView 열기를 요청하는 시점에 발급하세요.

### WebView에서 결제도 되나요?

아니요. WebView는 부스트와 쿠폰 기능만 제공합니다. 결제 등록은 게임 서버에서 [Server API](/ko/guides/developers/game-integration/payment)를 직접 호출해야 합니다.

### 동시에 여러 기기에서 접속하면 어떻게 되나요?

같은 유저는 하나의 세션만 허용됩니다. 새 기기에서 WebView를 열면 이전 기기의 세션이 자동으로 무효화되고, 이전 기기에서는 세션 만료 안내가 표시됩니다.
