H Honor
EN
DiDi finanzas

Documentation

Provides integration guides, API documentation, testing instructions and other information needed throughout the integration process

2.10 Refund Status Webhook

1. Webhook Tech Solution

BNPL api docs图 version 1-part2 - Webhook Tech Solution.jpg

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

Event Type Event Name Event Description
Payment PAYMENT.ORDER.PAID Payment Succeed
PAYMENT.ORDER.CANCELED Payment Canceled
PAYMENT.ORDER.DENIED Payment Denied
PAYMENT.ORDER.EXPIRED Payment Expired.
The order expires when it has not been paid.
The expiration time refers to the expireTime field in the request, which is passed in by the Partner.
If the partner does not pass in this value, it defaults to 24 hours.
Refund REFUND.ORDER.SUCCESS Return Successfully
REFUND.ORDER.FAILED Return Failed

3. Webhook Event Message Format

3.1 Payment Webhook

Header

Field Name Field Description Parameter Value Present (M/O/C) Remarks
Content-Type Content Type application/json M
X-Event-Timestamp Time stamp e.g., 1733619128 M The timestamp information.
Timestamp in seconds. Used for signature calculation.
X-Event-Signature signature M The signature information. The partner should validate this value.
X-Event-ID Unique event ID M unique event id
X-Event-Topic Event name M Event name. You can refer to the "2. Webhook Event List".
X-Event-Delivery-ID delivery id (uuid) e.g., "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" M Unique delivery ID (UUID) for this webhook attempt.
This field won't be calculated in the signature.



Body

Field Name Type Field Description Present (M/O/C) Remarks
partnerOrderId String Merchant-side payment document ID M Idempotent bond
Merchant payment serial number ID
partnerOrderAmount Long Order amount M The actual payment amount multiplied by 100; this amount is the final amount after applying the merchant's discount.
The unit is cents. Example: for 99.99 pesos, the value should be 9999.
Please use the amount corresponding to the Mexican Peso currency.
currency String Currency M ISO 4217 standard - Mexico MXN
expireTime String Expiration time O pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX
e.g., "2025-12-08T01:11:33.000+08:00". Timezone offset is mandatory.
finishTime String Completion time O pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX
e.g., "2025-12-08T01:11:33.000+08:00". Timezone offset is mandatory.
createTime String Order creation time O pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX
e.g., "2025-12-08T01:11:33.000+08:00". Timezone offset is mandatory.
updateTime String Order status latest update time O pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX
e.g., "2025-12-08T01:11:33.000+08:00". Timezone offset is mandatory.
rejectCode String Reject Code O Value Existed in DENIED State.
It exists only when X-Event-Topic is PAYMENT.ORDER.DENIED; for other event types (such as EXPIRED, CANCELED), this field does not exist or is an empty string.
rejectMsg String Rejection reason O Value Existed in DENIED State.
test Boolean Test order Remark flag. O Test means the test txn.



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

Field Name Field Description Parameter Value Present (M/O/C) Remarks
Content-Type Content Type application/json M
X-Event-Timestamp Time stamp e.g., 1733619128 M The timestamp information.
Timestamp in seconds. Used for signature calculation.
X-Event-Signature signature M The signature information. The partner should validate this value.
X-Event-ID unique event id M unique event id
X-Event-Topic Event name M Event name. You can refer to the "2. Webhook Event List".
X-Event-Delivery-ID delivery id (uuid) e.g., "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" M Unique delivery ID (UUID) for this webhook attempt.
This field won't be calculated in the signature.



Body

Field Name Type Field Description Present (M/O/C) Remarks
partnerOrderId String Merchant-side payment document ID M Idempotent bond
Merchant payment serial number ID
partnerRefundId String Merchant-side refund form ID M Idempotent bond
refundAmount Long Refund amount M Actual refund amount. The amount unit is cents. For example, if it's 99.99 pesos, the value here should be 9999.
Refund Amount = sum (Refund Unit Price × Refund Quantity)
currency String Currency M ISO 4217 standard - Mexico MXN
rejectCode String Reject Code O Value Existed in DENIED State
rejectMsg String Rejection reason O Value Existed in DENIED State
refundTime String Refund time M pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX
e.g., "2025-12-08T01:11:33.000+08:00". Timezone offset is mandatory.
createTime String Order creation time O pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX
e.g., "2025-12-08T01:11:33.000+08:00". Timezone offset is mandatory.
updateTime String Order status latest update time O pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX
e.g., "2025-12-08T01:11:33.000+08:00". Timezone offset is mandatory.
test Boolean Test order Remark flag. O Test means the test txn.



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
reject_code
TradePayFail
SkuBlackList
RiskRuleRejected
RiskControlRejected
PayRiskReject
PaylaterRuleRejected
OverdueRepayment
LoggedOut
GeneralError
ExitClose
CreditReject
CreditNotEnough
CreditActiveReject
CategoryBlackList

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).

Copied successfully