1. Webhook Tech Solution

When the partner creates a payment order or refund order, the message will be sent to the DiDi BNPL Core system.
The DiDi BNPL Webhook system will deliver the Webhook Event to the partner system.
2. Webhook Event List
3. Webhook Event Message Format
3.1 Payment Webhook
Header
Body
Example
{
"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
Example
{
"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. Reject Code List for PAYMENT.ORDER.DENIED
The reject_code in this list is returned only in PAYMENT.ORDER.DENIED events, to indicate the specific reason for the payment failure. Events such as PAYMENT.ORDER.EXPIRED and PAYMENT.ORDER.CANCELED do not return this field.
- rejectCode
5. Webhook URL & Signature Verification
5.1 Webhook URL
Please provide your production and testing environment Webhook URLs to the DiDi staff for configuration. DiDi will then send you the signing key corresponding to the Webhook.
5.2 Webhook Signature Verification
The returned Webhook contains a signature in its header. You need to develop the signature verification function for the Webhook message.
You can refer to the code below.
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 Signature verification tools
* Format: eventId:timestamp:data
* Signature result: hexadecimal string
*/
public class HMACSHA512HexSignerVerify {
private static final String HMAC_SHA512 = "HmacSHA512";
private static final String DELIMITER = ":";
/**
* Verify signature
*
* @param eventId event ID, value description: X-Event-ID in the request header
* @param data data, value description: The complete response of the request
* @param signature The signature to be verified (hexadecimal string), value description: X-Event-Signature in the request header
* @param secretKey key, value description: The unique signature key generated when a tenant adds a subscription
* @param timestamp timestamp (in seconds), take X-Event-Timestamp from the header
* @param maxAgeSeconds signature maximum validity period (seconds), 0 indicates no validity period verification
* @return verification result
*/
public static boolean verifySignature(
String eventId,
String data,
String signature,
String secretKey,
long timestamp,
long maxAgeSeconds) {
try {
// Verify the validity period of the timestamp
if (maxAgeSeconds > 0) {
long currentTime = Instant.now().getEpochSecond();
if (Math.abs(currentTime - timestamp) > maxAgeSeconds) {
return false; // The signature has expired.
}
}
// Recalculate the signature
String expectedSignature = generateSignature(eventId, data, secretKey, timestamp);
// Security comparison of the signature
return constantTimeEquals(expectedSignature.toLowerCase(), signature.toLowerCase());
} catch (Exception e) {
return false;
}
}
/**
* Generate a signature (hexadecimal string with a timestamp)
*
* @param eventId event ID
* @param data data
* @param secretKey key
* @param timestamp Timestamp (in seconds)
* @return hexadecimal string signature
*/
public static String generateSignature(String eventId, String data, String secretKey, long timestamp) {
try {
// Build the payload
String payload = eventId + DELIMITER + timestamp + DELIMITER + data;
// Calculate 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));
// Convert to a hexadecimal string
return bytesToHex(hashBytes);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to generate the signature", e);
}
}
/**
* Convert a byte array to a hexadecimal string
*
* @param bytes byte array
* @return hexadecimal string
*/
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();
}
/**
* Secure string comparison (preventing timing attacks)
*
* @param str1 string1
* @param str2 string2
* @return whether they are equal
*/
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 Delivery & Retry Policy
Maximum Delivery Attempts: Total 10 attempts (including the initial normal delivery, with 9 automatic retries after failure).
Retry Trigger Condition: The partner system does not return an http code within [200, 300) when receiving the Webhook event.
Retry Strategy: Exponential backoff strategy with a base of 2.
Retry Timing Rule: Assume the first retry time after the initial delivery failure is t; the trigger time for each subsequent retry in the queue is as follows: t, t+2, t+4, t+8, t+16, t+32, t+64, t+256, t+512, t+1024 (Unit: second).