1. Webhook 技术解决方案

当机构创建支付订单或退款订单时,消息将发送到滴滴BNPL核心系统。
滴滴BNPL Webhook系统将Webhook事件传递给机构(or 合作伙伴)系统。
2. Webhook 事件列表
3 Webhook 事件消息格式
3.1 Payment Webhook
Header
Body
例子
{
"partnerOrderId": "202512XXXX031300011032631762517",
"partnerOrderAmount": 52231,
"currency": "MXN",
"expireTime": "2025-12-09T01:11:33.000+08:00",
"finishTime": "2025-12-08T01:12:08.000+08:00",
"createTime": "2025-12-08T01:11:33.000+08:00",
"updateTime": "2025-12-08T01:12:08.000+08:00",
"rejectCode": "",
"rejectMsg": "",
"test": true
}
3.2 Refund Webhook
Header
Body
例子
{
"partnerOrderId": "16000227_20260XXXX796022901278864",
"partnerRefundId": "16000227_KBOtXXXX20260127202943864",
"refundAmount": 9999,
"currency": "MXN",
"rejectCode": "",
"rejectMsg": "",
"refundTime": "2026-01-27T20:29:43.000+08:00",
"createTime": "2026-01-27T20:29:43.000+08:00",
"updateTime": "2026-01-27T20:29:48.000+08:00",
"test": true
}
4. PAYMENT.ORDER.DENIED 的拒绝码列表
本列表中的 reject_code 仅在 PAYMENT.ORDER.DENIED 事件中返回,用于指示支付失败的具体原因。PAYMENT.ORDER.EXPIRED、PAYMENT.ORDER.CANCELED 等事件不返回此字段。
- rejectCode
5. Webhook URL & Webhook 验签
5.1 Webhook URL
请将您的生产和测试环境的Webhook URL提供给DiDi员工进行配置,随后DiDi会将Webhook对应的密钥发送给您。
5.2 Webhook 验签
关于返回的Webhook,其Header中包含了签名,需要您开发Webhook消息的签名验证功能。
你可以参考下面的代码。
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
/**
* HMAC-SHA512 验签工具类
* 格式:eventId:timestamp:data
* 签名结果:16进制字符串
*/
public class HMACSHA512HexSignerVerify {
private static final String HMAC_SHA512 = "HmacSHA512";
private static final String DELIMITER = ":";
/**
* 验证签名
*
* @param eventId 事件ID, 取值说明: 请求header中的X-Event-ID
* @param data 数据, 取值说明: 请求的完整响应
* @param signature 待验证的签名(16进制字符串), 取值说明: 请求header中的X-Event-Signature
* @param secretKey 密钥, 取值说明: 租户添加订阅时生成的唯一签名秘钥
* @param timestamp 时间戳(秒), 取header中的X-Event-Timestamp
* @param maxAgeSeconds 签名最大有效期(秒),0表示不验证有效期
* @return 验证结果
*/
public static boolean verifySignature(
String eventId,
String data,
String signature,
String secretKey,
long timestamp,
long maxAgeSeconds) {
try {
// 验证时间戳有效期
if (maxAgeSeconds > 0) {
long currentTime = Instant.now().getEpochSecond();
if (Math.abs(currentTime - timestamp) > maxAgeSeconds) {
return false; // 签名已过期
}
}
// 重新计算签名
String expectedSignature = generateSignature(eventId, data, secretKey, timestamp);
// 安全比较签名
return constantTimeEquals(expectedSignature.toLowerCase(), signature.toLowerCase());
} catch (Exception e) {
return false;
}
}
/**
* 生成签名(16进制字符串,带时间戳)
*
* @param eventId 事件ID
* @param data 数据
* @param secretKey 密钥
* @param timestamp 时间戳(秒)
* @return 16进制字符串签名
*/
public static String generateSignature(String eventId, String data, String secretKey, long timestamp) {
try {
// 构建payload
String payload = eventId + DELIMITER + timestamp + DELIMITER + data;
// 计算HMAC-SHA512
Mac mac = Mac.getInstance(HMAC_SHA512);
SecretKeySpec secretKeySpec = new SecretKeySpec(
secretKey.getBytes(StandardCharsets.UTF_8),
HMAC_SHA512
);
mac.init(secretKeySpec);
byte[] hashBytes = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
// 转换为16进制字符串
return bytesToHex(hashBytes);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("生成签名失败", e);
}
}
/**
* 字节数组转16进制字符串
*
* @param bytes 字节数组
* @return 16进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
/**
* 安全的字符串比较(防止时序攻击)
*
* @param str1 字符串1
* @param str2 字符串2
* @return 是否相等
*/
private static boolean constantTimeEquals(String str1, String str2) {
if (str1 == null || str2 == null) {
return false;
}
if (str1.length() != str2.length()) {
return false;
}
int result = 0;
for (int i = 0; i < str1.length(); i++) {
result |= str1.charAt(i) ^ str2.charAt(i);
}
return result == 0;
}
}
6 Webhook 投递与重试规则
最大投递次数:共计10次(含首次投递,失败后自动重试9次)
重试触发条件:机构(or 合作伙伴)系统收到Webhook事件时返回的 http code 没有在 [200, 300) 区间。
重试策略:采用底数为2的指数退避重试策略
重试时间规则:设首次投递失败后的首次重试时间为t,后续每一轮重试队列的触发时间依次为:t、t+2、t+4、t+8、t+16、t+32、t+64、t+256、t+512、t+1024 (单位:秒)