# 实现服务器到服务器兑换回调

> 通过设置服务器到服务器的回调，可以安全地验证奖励广告完整观看情况，从而帮助防止欺诈并确保准确分发奖励。

在[奖励玩家](/grow/ads/unity-sdk/rewarded-ads.md)时可使用服务器到服务器 (S2S) 回调来检测和防止作弊。

## 工作原理##how-it-works

当玩家完整观看视频广告时，Unity 广告服务器会向您指定的 URL 发送已签名的回调。这个过程发生在视频实际结束之前，因此可以在玩家返回游戏之前完成奖励循环。

![S2S 赎回回调过程的图表。](/api/media?file=/grow/media/images/s-2-s-redeem-callback-process.png)

> **Tip:**
>
> 根据流量，回调可能需要一些时间才能到达。为确保
> 流畅的游戏体验，请立即奖励玩家，然后使用 S2S 回调
> 进行完整性检查以防作弊。为避免在视频结束之前分散玩家的注意力，
> 应在广告观看结束之后再显示奖励通知。

## 实现##implementation

要使用 S2S 回调，您需要在展示广告之前设置服务器 ID (sid)。默认情况下，S2S 兑换回调不可用。如果您需要为项目启用这些回调，[请联系 Unity Ads 支持团队](https://support-ads.unity.com/s/ContactUs)，并在消息中提供您的 [Game ID（游戏 ID）](/grow/dashboard/get-started/project/settings.md)及其各自的回调 URL。Unity 将向您发送用于签名和验证回调的密钥哈希值。

### 示例##examples

1. **Unity (C#)**

   要在 C# 代码中实现回调，请将  值设置为您的服务器 ID，然后通过 `ShowOptions.gamerSid`(/grow/ads/unity-sdk/unity-api)`ShowOptions.gamerSid` 方法传递 options 对象。

   ```cs
   using UnityEngine;
   using System.Collections;
   using UnityEngine.Advertisements;

   public class UnityAdsManager :MonoBehaviour
   {
     public string gameId;
     public string placement = "rewardedVideo"

     public void ShowAd() {

     ShowOptions options = new ShowOptions();

     // setting the server ID
     options.gamerSid = "your-side-id";

       Advertisement.Show(placementID, options);
     }
   }
   ```

2. **Android (Java)**

   要在 Java 代码中实现回调，请将 `PlayerMetaData.setServerId` 值设置为您的服务器 ID。

   ```java
   PlayerMetaData playerMetaData = new PlayerMetaData(context);
   playerMetaData.setServerId("example");
   playerMetaData.commit();

   UnityAds.show(activity);
   ```

3. **iOS (Objective-C)**

   要在 Objective-C 代码中实现回调，请将 `playerMetaData.setServerId` 值设置为您的服务器 ID。

   ```objective-c
   id playerMetaData = [[UADSPlayerMetaData alloc] init];
   [playerMetaData setServerId:@"example"];
   [playerMetaData commit];

   [UnityAds show: self
     placementId: placementId
     showDelegate: showDelegate];
   ```

## 回调信息##callback-information

### 回调来源##callback-origin

回调将来自于[此处](https://static.applifier.com/public_ips.json)列出的 IP 地址/网络。该列表将在每个月的第一天更新。发行商可以放心忽略或阻止来自任何其他地方的回调。

### 回调 URL 格式##callback-url

该请求是对以下格式的 URL 的 HTTP/1.1 `GET` 请求：

```text
[CALLBACK_URL][SEPARATOR1]sid=[SID][SEPARATOR]oid=[OID][SEPARATOR]hmac=[SIGNATURE]
```

有关 query 参数的信息，请参阅下表：

| **参数**         | **内容**                                                                                                                                            |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CALLBACK_URL` | 回调 URL 的基 URL，例如：`https://developer.example.com/award.php?productid=1234`。要配置此参数，[请联系 Unity Ads 支持团队](https://support-ads.unity.com/s/ContactUs)。 |
| `SEPARATOR1`   | 如果 URL 中还不存在 `?`，请使用 `?`。否则，使用 `&#x26;`。                                                                                                          |
| `SID`          | 用户 ID 或要发送到终端的任何自定义数据。                                                                                                                            |
| `SEPARATOR`    | `&#x26;`                                                                                                                                          |
| `OID`          | Unity Ads 服务器生成的唯一 Offer ID（优惠 ID）。                                                                                                               |
| `SEPARATOR`    | `&#x26;`                                                                                                                                          |
| `SIGNATURE`    | 参数字符串的 HDMAC-MD5 哈希值，例如：`106ed4300f91145aff6378a355fced73`。                                                                                       |

```text
http title="Example callback URL"
https://developer.example.com/award.php?productid=1234&amp;sid=1234567890&amp;oid=0987654321&amp;hmac=106ed4300f91145aff6378a355fced73
```

### 对回调 URL 进行签名##sign-the-callback-url

回调 URL 请求会将一个签名附加到 URL 参数。该签名是通过以键值形式连接所有 URL 参数（HMAC 除外，按字母顺序排列并以逗号分隔）创建的参数字符串的 HDMAC-MD5 哈希值。

例如，一个包含 SID 和 OID 的回调 URL

```text
https://developer.example.com/award.php?productid=1234&sid=1234567890&oid=0987654321
```

将具有以下参数字符串：

```text
oid=0987654321,productid=1234,sid=1234567890
```

此字符串与您即将从支持团队收到的密钥进行哈希处理后，返回一个由奖励回调触发为 URL 的哈希值。例如：

```text
https://developer.example.com/award.php?productid=1234&sid=1234567890&oid=0987654321&hmac=106ed4300f91145aff6378a355fced73
```

> **Important:**
>
> 回调 URL 中包含的所有参数必须按字母顺序用于签名计算。否则，签名将不匹配。

### 回调响应##callback-response

如果该请求通过了所有检查并且用户获得了奖励物品，则 URL 必须以 `HTTP/1.1 200 OK` 响应作为应答并在 HTTP 请求的正文中包含字符 `1`。例如：

```text
http title="Callback response example"
HTTP/1.1 200 OK
Date: Wed, 22 Feb 2012 23:59:59 GMT
Content-Length: 8
1
```

如果出现错误（例如，OID 已被使用，或签名不匹配，或者当用户未能获得承诺物品时的任何其他错误），服务器应返回 `400` 或 `500` 范围内的 HTTP 错误，并显示人类可读的错误。例如：

```text
http title="Callback error response example"
HTTP/1.1 400 ERROR
Date: Wed, 22 Feb 2012 23:59:59 GMT
Content-Length: 12

Duplicate order
```

#### node.js 中的回调##callbacks-in-node.js

以下示例演示了如何使用 node.js + express 来验证签名：

```text
js title="node.js callback example"
// NODE.js S2S callback endpoint sample implementation
// Unity Ads

var express = require("express");
var crypto = require("crypto");
var app = express();

app.listen(process.env.PORT || 3412);

function getHMAC(parameters, secret) {
  var sortedParameterString = sortParams(parameters);
  return crypto
    .createHmac("md5", secret)
    .update(sortedParameterString)
    .digest("hex");
}

function sortParams(parameters) {
  var params = parameters || {};
  return Object.keys(params)
    .filter((key) => key !== "hmac")
    .sort()
    .map((key) => (params[key] === null ? `${key}=` : `${key}=${params[key]}`))
    .join(",");
}

app.get("/", function (req, res) {
  var sid = req.query.sid;
  var oid = req.query.oid;
  var hmac = req.query.hmac;

  // Save the secret as an environment variable.If none is set, default to xyzKEY
  var secret = process.env.UNITYADSSECRET || "xyzKEY";

  var newHmac = getHMAC(req.query, secret);

  if (hmac === newHmac) {
    // Signatures match

    // Check for duplicate oid here (player already received reward) and return 403 if it exists

    // If there's no duplicate - give virtual goods to player.Return 500 if it fails.

    // Save the oid for duplicate checking.Return 500 if it fails.

    // Callback passed, return 200 and include '1' in the message body
    res.status(200).send("1");
  } else {
    // no match
    res.sendStatus(403);
  }
});
```

## PHP 中的回调##callbacks-in-php

以下示例演示了如何在 PHP 中验证签名：

```text
php title="PHP callback example"
<?php
function generate_hash($params, $secret) {
  ksort($params); // All parameters are always checked in alphabetical order
  $s = '';
  foreach ($params as $key => $value) {
    $s .= "$key=$value,";
  }
  $s = substr($s, 0, -1);
  $hash = hash_hmac('md5', $s, $secret);
  return $hash;
}

$hash = $_GET['hmac'];
unset($_GET['hmac']);
$signature = generate_hash($_GET, 'xyzKEY'); // insert here the secret hash key you received from Unity Ads support
error_log("req hmac".$hash);
error_log("sig hmac".$signature);

// check signature
if($hash != $signature) { header('HTTP/1.1 403 Forbidden'); echo "Signature did not match"; exit; }

// check duplicate orders
if(check_duplicate_orders($_GET['oid']) { header('HTTP/1.1 403 Forbidden'); echo "Duplicate order"; exit; }

// if not then give the player the item and check that it succeeds.
if(!give_item_to_player($_GET['sid'], $_GET['product']) { header('HTTP/1.1 500 Internal Server Error'); echo "Failed to give item to the player"; exit; }

// save the order ID for duplicate checking
if(save_order_number($_GET['oid']) { header('HTTP/1.1 500 Internal Server Error'); echo "Order ID saving failed, user granted item"; exit; }

// everything OK, return "1"
header('HTTP/1.1 200 OK');
echo "1";
?>
```
