> For the complete documentation index, see [llms.txt](https://onestore-dev.gitbook.io/dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://onestore-dev.gitbook.io/dev/tools/web-sdk/iaa.md).

# 04. 인앱 광고 (IAA)

### **지원 광고 유형**

SDK는 두 가지 광고 유형을 지원합니다.

<table><thead><tr><th width="150">유형</th><th width="300">API</th><th>보상</th></tr></thead><tbody><tr><td>리워드 광고</td><td><code>sdk.ads.showRewardedAsync()</code></td><td>시청 완료 시 게임 내 보상 지급</td></tr><tr><td>전면 광고</td><td><code>sdk.ads.showInterstitialAsync()</code></td><td>보상 없음 (스테이지 전환 등 자연 구간 노출)</td></tr></tbody></table>

{% hint style="warning" %}
**필수: 광고 지면 ID(placementId) 사전 발급**

광고를 요청하려면 광고 지면 ID(placementId)를 사전에 발급받아야 합니다. 지면 ID는 원스토어 개발자센터에서 게임별로 발급되며(리워드·전면 각각), 발급되지 않은 지면으로 요청하면 광고는 폴백 없이 실패합니다. 지면 발급 절차는 [수익화>인앱 광고](https://onestore-dev.gitbook.io/dev/docs/apps/product/monetization/iaa) 부분을 참고하세요.
{% endhint %}

### **광고 기능 지원 여부 확인: isSupported**

<table><thead><tr><th width="300">호출</th><th width="100">반환</th><th>의미</th></tr></thead><tbody><tr><td><code>sdk.ads.isSupported()</code></td><td>boolean</td><td>리워드 · 전면을 모두 지원하면 true</td></tr><tr><td><code>sdk.ads.isSupported("rewarded")</code></td><td>boolean</td><td>리워드 광고 지원 여부</td></tr><tr><td><code>sdk.ads.isSupported("interstitial")</code></td><td>boolean</td><td>전면 광고 지원 여부</td></tr></tbody></table>

```javascript
// initializeAsync() 이후에 확인하고, 지원하는 경우에만 버튼을 노출합니다
if (sdk.ads.isSupported("rewarded")) {
  showRewardButton();
}
```

* 판정 근거는 초기화 응답으로 확정되므로 `initializeAsync()` **이후**에 호출하십시오. 그 전에는 낙관적으로 true가 반환될 수 있습니다.
* 광고 재고 여부(`isReadyAsync`)와는 다른 개념입니다. `isSupported`는 "이 앱 버전이 이 광고 기능을 제공하는가", `isReadyAsync`는 "지금 띄울 광고가 있는가"입니다.
* 미지원 상태에서 광고를 요청하면 앱 호출 없이 `{ status: "failed", reason: "platform_not_supported" }`가 반환됩니다.

### **광고 요청: showRewardedAsync / showInterstitialAsync**

두 API 모두 파라미터를 JSON(객체) 형식으로 받습니다.

```javascript
// 리워드 광고
async function showRewardedAd() {
  var requestId = await issueRequestIdFromMyServer();   // 게임사 서버 발급 권장

  var result = await sdk.ads.showRewardedAsync({
    placementId: "REWARD_PLACEMENT_ID",
    requestId: requestId
  });

  if (result.status === "rewarded") {
    // 광고 노출 신호일 뿐입니다 — 실제 지급은 자사 서버에서 포스트백(SSV) 확인 후 반영
    playRewardEffect();
    await checkRewardOnMyServer(result.requestId);
    return;
  }

  if (result.status === "dismissed") {
    showToast("광고를 끝까지 시청해야 보상을 받을 수 있습니다.");
    return;
  }

  // status === "failed" — 보상 대상이 아닙니다 (no_fill 포함)
  if (result.reason === "no_fill") {
    showToast("지금은 표시할 광고가 없습니다.");
  }
  continueWithoutAd();               // 광고 없이 진행할 대체 경로
}

// 전면 광고
async function showInterstitialAd() {
  var r = await sdk.ads.showInterstitialAsync({
    placementId: "INTERSTITIAL_PLACEMENT_ID"
  });
  // r.status 는 "completed" 또는 "failed" — 어느 쪽이든 게임 진행은 계속합니다
  goToNextStage();
}
```

**요청 파라미터**

<table><thead><tr><th width="130">파라미터</th><th width="80">타입</th><th width="120">필수 여부</th><th>설명</th></tr></thead><tbody><tr><td>placementId</td><td>string</td><td>필수</td><td>사전 발급받은 광고 지면 ID. 누락 시 앱 호출 없이 즉시 <code>{ status: "failed", reason: "invalid_request" }</code>가 반환됩니다</td></tr><tr><td>requestId</td><td>string</td><td>리워드 광고 필수</td><td>보상 지급 추적 키. <strong>필수 값</strong>입니다. 포스트백 수신 시 어느 유저의 보상인지 확인하려면 게임사 서버가 발급하고 유저와 매핑해 두는 것을 <strong>권장</strong>합니다. SDK는 이 값을 자동 생성하지 않으므로 게임이 직접 전달해야 합니다. 누락 시 앱 호출 없이 즉시 <code>{ status: "failed", reason: "invalid_request" }</code>가 반환됩니다. 전면 광고에서는 사용하지 않습니다</td></tr><tr><td>timeoutMs</td><td>number</td><td>선택</td><td>광고 결과 대기 한도(ms). 광고 <strong>종료 응답까지</strong> 적용됩니다. 기본 600000(10분), 허용 범위 1000~1800000(30분). 범위 밖 값은 기본값(600000)으로 대체됩니다. 기본값이 체험형(플레이어블) 등 노출이 긴 광고도 충분히 덮으므로 보통 지정할 필요가 없습니다</td></tr></tbody></table>

**반환값 (AdResult)**

광고 API는 실패해도 reject하지 않고 아래 객체로 resolve합니다.

<table><thead><tr><th width="120">필드</th><th width="180">존재 조건</th><th>설명</th></tr></thead><tbody><tr><td>status</td><td>항상</td><td>리워드: <code>"rewarded"</code>(시청 완료) / <code>"dismissed"</code>(중도 이탈) / <code>"failed"</code> · 전면: <code>"completed"</code> / <code>"failed"</code></td></tr><tr><td>requestId</td><td>리워드 광고</td><td>요청 시 전달한 추적 키를 그대로 반환합니다. <strong>실패 · 타임아웃 시에도 반환</strong>되므로 요청 대조에 사용할 수 있습니다. 전면 광고 결과에는 없습니다</td></tr><tr><td>reason</td><td>status가 <code>"failed"</code>일 때</td><td>실패 사유 문자열 (예: <code>"no_fill"</code>). <a href="/dev/tools/web-sdk/errors.md">06. 오류 사유 (reason)</a> 참고. 분기 조건에 사용하십시오</td></tr></tbody></table>

{% hint style="danger" %}
**보상 지급 원칙**

* `status: "rewarded"`는 **광고 노출 신호일 뿐, 지급 근거가 아닙니다.** 실제 보상 확정은 광고 서버 검증을 거쳐 게임사 서버로 전달되는 포스트백(SSV)으로 이루어지며, 게임은 자사 서버에 지급 여부를 조회한 뒤 반영해야 합니다.

* 따라서 리워드 광고의 `requestId`는 게임사가 발급하여 포스트백 수신 시 어느 유저의 보상인지 확인할 수 있어야 합니다.

* 보상 연출은 `status`가 `"rewarded"`인 경우에만 하십시오. `"dismissed"`(중도 이탈)와 `"failed"`는 보상 대상이 아닙니다. 특히 **재고 부족(`reason: "no_fill"`)으로 광고가 표시되지 못한 경우에는 보상을 지급하지 않습니다.** 보상을 주는 대신, 게임 진행이 막히지 않도록 대체 경로를 두십시오.
  {% endhint %}

* 실패는 예외(throw)가 아니라 `{ status: "failed", reason }`으로 반환됩니다. 광고는 재고 부족(no\_fill) 등으로 언제든 실패할 수 있으므로 게임 진행이 막히지 않게 처리하십시오.

* 광고 표시 구간에는 `pause` / `resume` 이벤트가 전달됩니다 ([03. Lifecycle 관리](/dev/tools/web-sdk/lifecycle.md) 참고). 게임은 pause 리스너 한 곳에서 처리하면 됩니다.

* 광고 · 결제 화면은 동시에 1건만 표시됩니다. 표시 중 추가 광고 요청은 `{ status: "failed", reason: "already_showing" }`으로 즉시 반환됩니다.

* `timeout` 실패 이후에도 광고가 뒤늦게 노출될 가능성이 있습니다. 같은 지면의 즉시 재요청은 피하고, 반환된 `requestId`로 중복 지급을 방어하십시오.

### **광고 사전 로딩 · 재고 조회 (Optional)**

광고 노출 시점의 응답 시간을 줄이기 위해, 노출보다 앞서 로딩을 요청해 둘 수 있습니다.

```javascript
// 사전 로딩 — 접수만 되고 완료는 회신되지 않습니다
sdk.ads.loadRewarded({ placementId: "REWARD_PLACEMENT_ID" });
sdk.ads.loadInterstitial({ placementId: "INTERSTITIAL_PLACEMENT_ID" });

// 재고 조회 — 두 인수를 모두 넘기십시오
var ready = await sdk.ads.isReadyAsync("rewarded", "REWARD_PLACEMENT_ID");
```

<table><thead><tr><th width="260">API</th><th>파라미터</th><th width="170">반환</th></tr></thead><tbody><tr><td><code>sdk.ads.loadRewarded(options)</code><br><code>sdk.ads.loadInterstitial(options)</code></td><td><code>options.placementId</code> (string, 필수)</td><td>없음 (단방향 요청)</td></tr><tr><td><code>sdk.ads.isReadyAsync(type, placementId)</code></td><td><code>type</code>: <code>"rewarded"</code> 또는 <code>"interstitial"</code> (필수)<br><code>placementId</code>: 조회할 지면 ID (필수). 누락하면 앱에 묻지 않고 false가 반환됩니다</td><td><code>Promise&#x3C;boolean></code></td></tr></tbody></table>

* 사전 로딩은 선택 사항입니다. 호출하지 않아도 노출 요청 시 내부적으로 로딩됩니다 (단, 첫 노출까지의 대기가 길어질 수 있습니다).
* 사전 로딩은 접수만 되고 완료는 회신되지 않습니다. 같은 지면을 반복 호출해도 중복 로드가 발생하지 않습니다. `placementId` 누락 시 요청 자체가 무시됩니다.
* `isReadyAsync`는 boolean으로 resolve합니다. 결과는 **참고값**입니다. true여도 노출 시점의 재고 상황에 따라 실패할 수 있습니다. 등록되지 않은 지면 · 지면 유형 불일치 · 조회 실패 · `placementId` 누락은 모두 false로 반환됩니다 (오류를 던지지 않습니다).
