你只需要接入绑定流程
BindPact 不参与用户每次输入验证码时的验证。平台仍然是 TOTP Secret 的长期持有方和唯一验证方。
调用频率很低:绑定阶段创建会话并查询一次结果;日常登录、提现或敏感操作无需请求 BindPact。
获取平台凭证
每个合作平台会获得三项独立凭证。请存入服务端 Secret Manager,不要下发到浏览器或移动端。
平台在 BindPact 中的身份标识。
通过 X-Platform-Key 请求头鉴权,只显示一次。
配置 webhook 时签发,用于验证状态通知。
不要把 API Key、Webhook Secret 或 TOTP Secret 写入前端代码、二维码、URL、日志和分析系统。
生成 Secret 并创建绑定会话
- 生成并加密保存 Secret。使用 20 个密码学安全随机字节,编码为 32 位无填充 Base32。固定参数为 SHA1、6 位、30 秒。
- 保存一个稳定的幂等键。同一次用户操作发生网络重试时,必须复用完全相同的请求体和 Idempotency-Key。
- 从你的后端调用 BindPact。
platform_user_id使用平台内部稳定 ID;account_label只传脱敏提示。
curl -X POST 'https://api.BindPact.com/api/v1/platform/binding-sessions' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'X-Platform-Key: plt_REPLACE_ME' \
-H 'Idempotency-Key: 68b81cf0-d781-4ad1-9d2e-213e5967d90e' \
-d '{
"platform_user_id": "user_12345",
"account_label": "lin***@example.com",
"totp_secret": "JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP"
}'
成功响应
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "PENDING",
"qr_url": "one-device-token://bind/550e8400-e29b-41d4-a716-446655440000",
"expires_at": "2026-09-14T10:05:00+00:00",
"totp_algorithm": "SHA1",
"totp_digits": 6,
"totp_period": 30
}
把响应中的 qr_url 原样编码为二维码。二维码中绝不能包含 otpauth://、Secret 或平台 API Key。
只在 SUCCEEDED 后启用绑定
你可以短轮询状态接口,也可以接收签名 webhook。轮询请求示例:
curl 'https://api.BindPact.com/api/v1/platform/binding-sessions/550e8400-e29b-41d4-a716-446655440000' \
-H 'Accept: application/json' \
-H 'X-Platform-Key: plt_REPLACE_ME'
PENDING等待用户在 BindPact App 中确认AWAITING_ACKSecret 已交给 App,但尚未确认安全保存SUCCEEDED绑定成功,此时才可为账号启用 MFAREJECTED违反唯一绑定规则或前次 ACK 未完成EXPIRED会话过期,平台应保留原有安全状态AWAITING_ACK 不是成功。它只表示 Secret 已下发;App 写入 Keychain 并完成 ACK 后,状态才会变成 SUCCEEDED。
验证原始请求体签名
签名输入为 timestamp + "." + raw_body,算法为 HMAC-SHA256,小写十六进制结果加 v1= 前缀。
X-Webhook-Id事件唯一标识,用于幂等去重X-Webhook-TimestampUnix 秒,建议只接受 ±300 秒X-Webhook-Signaturev1=<64位小写hex><?php
$raw = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401); exit;
}
$expected = 'v1=' . hash_hmac(
'sha256',
$timestamp . '.' . $raw,
$_ENV['BINDPACT_WEBHOOK_SECRET']
);
if (!hash_equals($expected, $signature)) {
http_response_code(401); exit;
}
// 使用 X-Webhook-Id 做幂等去重,再处理 JSON。
在解析 JSON 前验签;已处理事件仍返回 2xx。只处理 binding.succeeded、binding.rejected 和 binding.expired。
日常验证码在平台本地验证
使用平台自己加密保存的 Secret 验证 RFC 6238 六位验证码。建议允许前后各一个时间窗口,并记录最后成功的 time-step 防止重放。
// 平台长期加密保存 Secret;验证时在本地完成
$counter = Totp::verifyAndGetCounter(
secret: $secret,
code: $sixDigitCode,
window: 1,
);
// 必须拒绝已经成功使用过的 time-step
$valid = $counter !== null && $counter > $lastSuccessfulCounter;