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

# リワード広告SSV

> リワード広告の報酬完了時にサーバーへ送信されるSSVコールバックURLを登録し、ペイロードを復号する方法をご案内します。

## 概要

リワード広告の**サーバーサイド検証（SSV, Server-Side Verification）** を使用すると、ユーザーが広告の視聴を完了した際に、Adropサーバーが指定したサーバーURLへコールバックを送信します。サーバー側で報酬付与を直接検証することで、不正を防止できます。

コールバックペイロードはAPIキーで暗号化されているため、URLが外部に露出してもAPIキーを持つ主体のみが内容を復号できます。

***

## 設定方法

<Steps>
  <Step title="APIキーの準備">
    SSVコールバックのペイロード復号に使用するAPIキーを先に発行してください。プロジェクトにアクティブなAPIキーが**1つ以上**ある場合にSSVを登録できます。

    発行方法は[連携 & APIキー](/ja/integrations/overview#apiキーの発行)ドキュメントを参照してください。
  </Step>

  <Step title="SSVの登録">
    コンソールの\*\*\[管理]\*\* > **\[連携]** > **リワード広告SSV**セクションで\*\*\[+ リワード広告SSVを登録]\*\*ボタンを押します。

    | 項目        | 説明                                         |
    | --------- | ------------------------------------------ |
    | **URL**   | 報酬完了時に呼び出すサーバーURL。`https://`で始まる必要があります。   |
    | **APIキー** | コールバックペイロードの復号に使用するAPIキー。デフォルトは最新のアクティブキー。 |
  </Step>

  <Step title="SDKでuserId / customDataを渡す">
    SDKで`setServerSideVerificationOptions()`を使って`userId`と`customData`を設定すると、その値がコールバックペイロードに含まれてサーバーへ送信されます。

    各プラットフォームの設定方法は以下のドキュメントを参照してください。

    * [Android リワード広告 — サーバーサイド検証](/ja/sdk/android/rewarded#サーバーサイド検証)
    * [iOS リワード広告 — サーバーサイド検証](/ja/sdk/ios/rewarded#サーバーサイド検証)
    * [React Native リワード広告 — サーバーサイド検証](/ja/sdk/react-native/rewarded#サーバーサイド検証)
    * [Flutter リワード広告 — サーバーサイド検証](/ja/sdk/flutter/rewarded#サーバーサイド検証ssv)
  </Step>
</Steps>

<Note>
  登録・編集時はHTTPS URLのみ許可され、プライベートIP（localhost、10.x、172.16〜31.x、192.168.x、169.254.x）はブロックされます。
</Note>

***

## リクエスト仕様

Adropサーバーは次のようにコールバックを送信します。

| 項目           | 値                      |
| ------------ | ---------------------- |
| Method       | `POST`                 |
| Content-Type | `application/json`     |
| リトライ         | 最大3回（0ms / 1s / 2sの間隔） |
| タイムアウト       | 1回あたり5秒                |
| 成功条件         | HTTP 200レスポンス          |

リクエストボディはAES-256-GCMで暗号化されて送信されます。

```json theme={null}
{
  "encrypted": "<ivHex>:<tagHex>:<ciphertextHex>"
}
```

各区間はコロン（`:`）で区切られたhex文字列です。復号後に得られる平文JSONの仕様は以下のとおりです。

| フィールド           | 型       | 説明                     |
| --------------- | ------- | ---------------------- |
| `project`       | string  | AdropプロジェクトID          |
| `app`           | string  | AdropアプリID             |
| `unit`          | string  | Adrop広告ユニットID          |
| `adNetwork`     | string  | 広告ネットワーク識別子            |
| `adUnit`        | string  | 広告ネットワーク内のユニット識別子      |
| `userId`        | string? | SDKで設定したユーザー識別子        |
| `customData`    | string? | SDKで設定したカスタムデータ        |
| `rewardItem`    | string  | 報酬アイテム名                |
| `rewardAmount`  | number  | 報酬数量                   |
| `transactionId` | string  | トランザクションID（重複防止用の一意な値） |
| `timestamp`     | number  | コールバック発生時刻（Unix ms）    |

<Note>
  `transactionId`は一意な値です。サーバーですでに処理した`transactionId`を再度受信した場合は、重複報酬を付与しないよう冪等（idempotent）に処理することを推奨します。
</Note>

***

## ペイロードの復号

AES-256-GCMの鍵は、**APIキー原文をSHA-256でハッシュした32バイト**です。

### Node.js

```javascript theme={null}
import { createDecipheriv, createHash } from 'node:crypto'
import express from 'express'

function decryptSsvPayload(encrypted, apiKey) {
  const [ivHex, tagHex, ciphertextHex] = encrypted.split(':')
  const key = createHash('sha256').update(apiKey).digest() // 32バイト

  const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(ivHex, 'hex'))
  decipher.setAuthTag(Buffer.from(tagHex, 'hex'))

  const plaintext = Buffer.concat([
    decipher.update(Buffer.from(ciphertextHex, 'hex')),
    decipher.final()
  ]).toString('utf8')

  return JSON.parse(plaintext)
}

const app = express()

app.post('/ssv-callback', express.json(), (req, res) => {
  const payload = decryptSsvPayload(req.body.encrypted, process.env.ADROP_API_KEY)
  // payload.userId, payload.customData, payload.rewardAmountなどで報酬付与処理
  res.sendStatus(200)
})
```

### Python

```python theme={null}
import hashlib
import json
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def decrypt_ssv_payload(encrypted: str, api_key: str) -> dict:
    iv_hex, tag_hex, ciphertext_hex = encrypted.split(':')
    key = hashlib.sha256(api_key.encode()).digest()  # 32バイト

    aesgcm = AESGCM(key)
    # AESGCMはciphertext + tagが連結された形式を想定
    plaintext = aesgcm.decrypt(
        bytes.fromhex(iv_hex),
        bytes.fromhex(ciphertext_hex) + bytes.fromhex(tag_hex),
        None
    )
    return json.loads(plaintext)
```

<Warning>
  SSVコールバックは、URLが外部に露出してもAdrop APIキーを持つ主体のみが復号できるよう設計されています。**SSVに連携されたAPIキーが漏洩した場合**は、サーバー側で直ちに廃棄し、連携メニューで新しいAPIキーに差し替えてください。
</Warning>

***

## 編集と削除

登録済みのSSVは、**リワード広告SSV**セクション右側のメニューから編集・削除できます。

* **編集**: URLまたは連携するAPIキーを変更します。
* **削除**: コールバックは呼び出されなくなります。削除後もSDKの`userId` / `customData`はAdrop内部のSSVログに保存されます。

***

## 関連ドキュメント

<CardGroup cols={2}>
  <Card title="連携 & APIキー" icon="key" href="/ja/integrations/overview">
    APIキーの発行・管理・廃棄方法
  </Card>

  <Card title="レポートAPI" icon="chart-line" href="/ja/integrations/reports">
    キャンペーン成果およびバックフィル収益データをAPIで照会する方法
  </Card>
</CardGroup>
