首页
游戏试玩 API 接入文档
APP
1
应用数
API
1
启动接口
DOCS
3015
游戏资源
类型统计
POST1
分组
| 名称 | 方式 | 接口 |
|---|---|---|
| 启动游戏 | POST | /api/launch |
接入概览
本文档面向第三方试玩站服务端接入。接入方根据平台提供的商户信息和游戏资源清单,在服务端请求启动接口,成功后将玩家跳转到平台返回的播放地址。
玩家点击游戏
接入方服务端签名
平台返回 play_url
302 跳转玩家
商户密钥只允许保存在服务端,不得出现在前端代码、App 包、公开仓库或日志明文中。
启动游戏
POST
{API_BASE_URL}/api/launch
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_id | string | 是 | 平台分配的商户 ID |
| launch_code | string | 是 | 游戏资源清单中的启动码 |
| timestamp | int | 是 | Unix 秒级时间戳,有效期 5 分钟 |
| nonce | string | 是 | 随机字符串,建议每次请求唯一 |
| signature | string | 是 | HMAC-SHA256 签名 |
| user_ip | string | 建议 | 玩家客户端 IP,用于安全校验 |
| visitor_id | string | 建议 | 同一玩家保持稳定 |
| user_agent | string | 建议 | 玩家浏览器 User-Agent |
签名规则
从请求 JSON 中移除 signature 字段,按参数名升序排序,使用 JSON 编码后,以商户密钥计算 HMAC-SHA256。
function canonicalPayload(payload):
remove payload.signature
sort payload by key ascending
return jsonEncode(payload, unescapedUnicode = true, unescapedSlash = true)
payload.signature = hmacSha256(canonicalPayload(payload), merchantSecret)
代码示例
PHP
<?php
$apiBaseUrl = 'https://api.example.com';
$merchantId = 'your_merchant_id';
$merchantSecret = 'your_merchant_secret';
$payload = [
'merchant_id' => $merchantId,
'launch_code' => 'g_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'timestamp' => time(),
'nonce' => bin2hex(random_bytes(8)),
'visitor_id' => 'v_1780000000abcdef',
'user_ip' => '203.0.113.10',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
];
$signPayload = $payload;
ksort($signPayload);
$canonical = json_encode($signPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$payload['signature'] = hash_hmac('sha256', $canonical, $merchantSecret);
$ch = curl_init($apiBaseUrl . '/api/launch');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode((string)$response, true);
if (($result['success'] ?? false) && !empty($result['data']['play_url'])) {
header('Location: ' . $result['data']['play_url'], true, 302);
exit;
}
Node.js
import crypto from 'node:crypto';
const apiBaseUrl = 'https://api.example.com';
const merchantId = 'your_merchant_id';
const merchantSecret = 'your_merchant_secret';
const payload = {
merchant_id: merchantId,
launch_code: 'g_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
timestamp: Math.floor(Date.now() / 1000),
nonce: crypto.randomBytes(8).toString('hex'),
visitor_id: 'v_1780000000abcdef',
user_ip: '203.0.113.10',
user_agent: 'Mozilla/5.0',
};
const canonical = JSON.stringify(
Object.fromEntries(Object.entries(payload).sort(([a], [b]) => a.localeCompare(b)))
);
payload.signature = crypto.createHmac('sha256', merchantSecret).update(canonical).digest('hex');
const response = await fetch(`${apiBaseUrl}/api/launch`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(payload),
});
const result = await response.json();
if (result.success && result.data?.play_url) {
// Web 框架中建议返回 302 跳转到 result.data.play_url
}
响应格式
成功响应
{
"success": true,
"message": "",
"data": {
"play_url": "https://play.example.com/xxx/index.html?...",
"play_ttl_seconds": 900,
"charged": 0.001
}
}
失败响应
{
"success": false,
"message": "签名无效",
"data": []
}
错误码
| HTTP 状态 | message 示例 | 处理建议 |
|---|---|---|
| 400/422 | 请求格式无效 / 缺少参数 | 检查 JSON 和必填参数 |
| 403 | 签名无效 | 检查签名字段、密钥和参数排序 |
| 403 | 服务器 IP 不在白名单 | 自行到商户端添加服务端出口 IP |
| 422 | 游戏不存在或未开通 | 检查 launch_code 是否来自最新资源清单 |
| 429 | 请求频率超限 | 增加防抖和服务端冷却 |
| 502 | 游戏启动失败,请稍后重试 | 稍后重试或联系平台排查 |
游戏资源
游戏资源清单独立提供,接入方前台展示游戏时使用 game_name、category、image_path;启动游戏时只传对应的 launch_code。
平台不提供可外链的 logo URL。交付包内只提供本地图片资源和相对路径映射,接入方需自行上传到自有静态目录、对象存储或 CDN,并在自己的前台使用自有图片地址。
{
"game_id": 1,
"game_name": "麻将胡了",
"category": "PG",
"launch_code": "g_d18d2c8a5d6f308aceb362c500750499",
"image_path": "../game-images/images/PG/1_g_d18d2c8a5d6f308aceb362c500750499.jpg"
}
联调清单
- 平台提供 API 地址、商户 ID、商户密钥和游戏资源清单。
- 接入方自行到商户端,填入IP白名单。
- 接入方根据游戏资源清单展示游戏列表。
- 接入方服务端生成签名并请求启动接口。
- 成功响应后将玩家跳转到
play_url。 - 测试签名错误、无效 launch_code、IP 未白名单等异常场景。