@@ -15,10 +15,19 @@ URL: provide the callback url in the [Create Payment](create-payment) request pa
1515| Parameter | Comments|
1616| :-----| ----- |
1717| Content-Type | application/json|
18- | timestamp | Current timestamp|
18+ | Version | API version (e.g., "5.00", "5.01"). Matches the version used when creating the order.|
19+ | timestamp | Current timestamp (Unix seconds, 10 digits)|
1920| publicKey | Merchant's RSA public key (optional, used for webhook signature verification)|
2021| signature | Generated by [ Signature Algorithm] ( #signature-algorithm ) |
2122
23+ :::tip Timestamp Format
24+ All timestamp fields in webhook callbacks use ** Unix timestamp in seconds** (10 digits):
25+ - Header ` timestamp ` : ` 1736236800 ` (seconds since epoch)
26+ - Body fields (` createTime ` , ` paymentTime ` , ` finishTime ` ): ` 1736233200 ` (seconds since epoch)
27+
28+ ** Important** : When verifying the signature, use the timestamp values exactly as they appear in the JSON body. Do NOT multiply by 1000.
29+ :::
30+
2231### Callback Request Parameters
2332| Parameter | Type | Comments|
2433| :----- | :-----| ----- |
@@ -27,11 +36,42 @@ URL: provide the callback url in the [Create Payment](create-payment) request pa
2736| | < ; [ RefundOrderType] ( data-model/refund-order ) > ; | Returned when "paymentType"=` E_COMMERCE_REFUND ` |
2837
2938### Signature Algorithm
30- #### Encrypt
31- 1 . Sort all fields in ascending alphabetical order by field name(key) in key=value format.
32- 2 . Generate a second timestamp and append it to the end of the sorted string in the format & ; timestamp=${ timestamp }
33- 3 . Encrypt string content with sha256 and sign with PKCS1V15,1024
34- 4 . Encode the encrypted bytes in base64
39+
40+ :::info Signature Method
41+ Bybit Pay webhook uses ** JSON body** for signature calculation. The signature string is constructed by concatenating:
42+
43+ ** ` timestamp ` + ` JSON_body ` **
44+
45+ Where:
46+ - ` timestamp ` : Current Unix timestamp in seconds (10 digits)
47+ - ` JSON_body ` : Complete JSON request body as a string (no sorting, no conversion)
48+
49+ ** Important** : Do NOT convert the JSON to URL-encoded format. Use the raw JSON string directly.
50+ :::
51+
52+ #### Sign (Bybit Pay Platform)
53+ 1 . Generate current timestamp (Unix seconds): ` timestamp = 1736236800 `
54+ 2 . Marshal the callback data to JSON string (preserve field order as-is)
55+ 3 . Concatenate: ` signContent = timestamp + jsonString `
56+ ```
57+ 1736236800{"paymentType":"E_COMMERCE","merchantId":"305142568",...}
58+ ```
59+ 4 . Encrypt with SHA256 and sign with RSA (PKCS1v15, 1024-bit key)
60+ 5 . Encode the signature bytes in base64
61+
62+ #### Verify (Merchant Side)
63+ 1 . Get ` timestamp ` and ` signature ` from request headers
64+ 2 . Read the raw request body as string (do not parse yet)
65+ 3 . Concatenate: ` signContent = timestamp + requestBody `
66+ ```
67+ 1736236800{"paymentType":"E_COMMERCE","merchantId":"305142568",...}
68+ ```
69+ 4 . Decode the signature from base64
70+ 5 . Verify the signature using SHA256 and Bybit's RSA public key
71+
72+ ---
73+
74+ #### Example: Sign & Verify
3575
3676For example, we sign the following data. The timestamp is ` 1736236800 ` , the RSA key is
3777``` shell
@@ -59,12 +99,13 @@ IavMyjrhDKyBGZ0mI6eoREaC4bxl31RRkYtg9mNeU3TxsBM=
5999-----END RSA PRIVATE KEY-----
60100```
61101
102+ ** JSON Payload Example:**
62103``` json
63104{
64105 "paymentType" : " E_COMMERCE" ,
65106 "merchantId" : " 305142568" ,
66107 "clientId" : " client_001" ,
67- "merchantTradeNo" : " a]f8c2d1 -5b3e-4a9f-b6c7-8d2e1f3a4b5c" ,
108+ "merchantTradeNo" : " af8c2d1 -5b3e-4a9f-b6c7-8d2e1f3a4b5c" ,
68109 "payId" : " 01JY2KM5QNPXR8S4HTJZT9BC12" ,
69110 "status" : " PAY_SUCCESS" ,
70111 "amount" : " 100" ,
@@ -75,31 +116,33 @@ IavMyjrhDKyBGZ0mI6eoREaC4bxl31RRkYtg9mNeU3TxsBM=
75116 "finishTime" : 1736233260
76117}
77118```
78- 1 . Sort all fields in ascending alphabetical order by field name(key) in key=value format.
79- ``` json
80- amount=100&clientId=client_001&createTime=1736233200000¤cy=USDT¤cyType=crypto&finishTime=1736233260000&merchantId=305142568&merchantTradeNo=a]f8c2d1-5b3e-4a9f-b6c7-8d2e1f3a4b5c&payId=01JY2KM5QNPXR8S4HTJZT9BC12&paymentTime=1736233260000&paymentType=E_COMMERCE&status=PAY_SUCCESS
81- ```
82- 2 . Generate a second timestamp and append it to the end of the sorted string in the format & ; timestamp=${ timestamp }
83- ``` json
84- amount=100&clientId=client_001&createTime=1736233200000¤cy=USDT¤cyType=crypto&finishTime=1736233260000&merchantId=305142568&merchantTradeNo=a]f8c2d1-5b3e-4a9f-b6c7-8d2e1f3a4b5c&payId=01JY2KM5QNPXR8S4HTJZT9BC12&paymentTime=1736233260000&paymentType=E_COMMERCE&status=PAY_SUCCESS×tamp=1736236800
85- ```
86- 3 . Encrypt string content with sha256 and sign with PKCS1V15,1024
87- 4 . Encode the encrypted bytes in base64
88- ``` shell
89- NgDZLZCVBdma904hzZXmU+fQ7dr7z7muZkuwAbDnibLXXXXXXXXXXXXgmzad58LfRtLXGlkNPXXXXXXXXXXX9jNYd6gxp7j0Mlh0vQlQCIb2283DQ3wbZDphilvXXXXXXXXXXXXX2IIBelYBBw39U=
90- ```
91- #### Decrypt
92- 1 . Get ` timestamp ` , ` signature ` from request header.
93- 2 . Get request body and unmarshal to structure or map structure.
94- 3 . Sort all fields in ascending alphabetical order by field name(key) in key=value format.
95- 4 . Generate a second timestamp and append it to the end of the sorted string in the format & ; timestamp=${ timestamp } .
96- 5 . decode the signature bytes from base64.
97- 6 . Encrypt string content with sha256 and verify signature base PKCS1V15,1024.
98- For example, we provided the following data.
99- ``` shell
100- NgDZLZCVBdma904hzZXmU+fQ7dr7z7muZkuwAbDnibLXXXXXXXXXXXXgmzad58LfRtLXGlkNPXXXXXXXXXXX9jNYd6gxp7j0Mlh0vQlQCIb2283DQ3wbZDphilvXXXXXXXXXXXXX2IIBelYBBw39U=
101- ```
102- The timestamp is ` 1736236800 ` , the RSA key is
119+
120+ ** Signature Calculation Steps:**
121+
122+ 1 . Generate timestamp: ` 1736236800 `
123+ 2 . Convert JSON to string (minified, no extra spaces):
124+ ```
125+ {"paymentType":"E_COMMERCE","merchantId":"305142568","clientId":"client_001","merchantTradeNo":"af8c2d1-5b3e-4a9f-b6c7-8d2e1f3a4b5c","payId":"01JY2KM5QNPXR8S4HTJZT9BC12","status":"PAY_SUCCESS","amount":"100","currency":"USDT","currencyType":"crypto","createTime":1736233200,"paymentTime":1736233260,"finishTime":1736233260}
126+ ```
127+ 3 . Concatenate: ` signContent = timestamp + jsonString `
128+ ```
129+ 1736236800{"paymentType":"E_COMMERCE","merchantId":"305142568",...}
130+ ```
131+ 4 . Sign with SHA256 + RSA private key
132+ 5 . Encode to base64:
133+ ```
134+ NgDZLZCVBdma904hzZXmU+fQ7dr7z7muZkuwAbDnibLXXXXXXXXXXXXgmzad58LfRtLXGlkNPXXXXXXXXXXX9jNYd6gxp7j0Mlh0vQlQCIb2283DQ3wbZDphilvXXXXXXXXXXXXX2IIBelYBBw39U=
135+ ```
136+
137+ ** Verification Steps (Merchant Side):**
138+
139+ 1 . Extract ` timestamp ` and ` signature ` from request headers
140+ 2 . Read raw request body (string, do not parse)
141+ 3 . Concatenate: ` signContent = timestamp + requestBody `
142+ 4 . Decode signature from base64
143+ 5 . Verify using SHA256 + RSA public key
144+
145+ ** Test Keys:**
103146``` shell
104147-----The following key pairs are for testing only-----
105148-----BEGIN RSA PUBLIC KEY-----
@@ -124,16 +167,8 @@ Bnj6KW1fk+UM29dUDjmTqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
124167IavMyjrhDKyBGZ0mI6eoREaC4bxl31RRkYtg9mNeU3TxsBM=
125168-----END RSA PRIVATE KEY-----
126169```
127- 1 . Sort all fields in ascending alphabetical order by field name(key) in key=value format.
128- ``` json
129- amount=100&clientId=client_001&createTime=1736233200000¤cy=USDT¤cyType=crypto&finishTime=1736233260000&merchantId=305142568&merchantTradeNo=a]f8c2d1-5b3e-4a9f-b6c7-8d2e1f3a4b5c&payId=01JY2KM5QNPXR8S4HTJZT9BC12&paymentTime=1736233260000&paymentType=E_COMMERCE&status=PAY_SUCCESS
130- ```
131- 2 . Generate a second timestamp and append it to the end of the sorted string in the format & ; timestamp=${ timestamp }
132- ``` json
133- amount=100&clientId=client_001&createTime=1736233200000¤cy=USDT¤cyType=crypto&finishTime=1736233260000&merchantId=305142568&merchantTradeNo=a]f8c2d1-5b3e-4a9f-b6c7-8d2e1f3a4b5c&payId=01JY2KM5QNPXR8S4HTJZT9BC12&paymentTime=1736233260000&paymentType=E_COMMERCE&status=PAY_SUCCESS×tamp=1736236800
134- ```
135- 3 . decode the signature bytes from base64
136- 4 . Encrypt string content with sha256 and verify signature base PKCS1V15,1024
170+
171+ ---
137172
138173
139174### Request Example
@@ -219,4 +254,61 @@ Content-Type: application/json
219254 "amount": "50",
220255 "createTime": 1736234000
221256}
222- ```
257+ ```
258+
259+ #### Callback Broker Prefund Order
260+
261+ :::info Broker Prefund Feature
262+ This webhook is triggered for broker prefund orders (payment type: ` BROKER_PREFUND ` ). It notifies merchants about cryptocurrency exchange/conversion order status updates.
263+
264+ ** Use Case** : When a merchant processes a fiat-to-crypto or crypto-to-fiat conversion through Bybit's broker service.
265+ :::
266+
267+ ``` http
268+ POST ${webhook url} HTTP/1.1
269+ Host: www.merchant.com
270+ Version: 5.01
271+ timestamp: 1736235000
272+ signature: NgDZLZCVBdma904hzZXmU+fQ7dr7z7muZkuwAbDnibLXXXXXXXXXXXXgmzad58Lf...
273+ Content-Type: application/json
274+
275+ {
276+ "tradeNo": "DL202601280001",
277+ "status": "completed",
278+ "quoteTxId": "QUOTE-20260128-001",
279+ "exchangeRate": "1.0025",
280+ "fromCoin": "USD",
281+ "fromCoinType": "fiat",
282+ "toCoin": "USDT",
283+ "toCoinType": "crypto",
284+ "fromAmount": "1000.00",
285+ "toAmount": "998.75",
286+ "createdAt": "1736235000000",
287+ "subUserid": "12345678"
288+ }
289+ ```
290+
291+ ** Broker Prefund Order Fields:**
292+
293+ | Field | Type | Description |
294+ | :------| :-----| :------------|
295+ | tradeNo | string | Broker deal order ID |
296+ | status | string | Order status: ` pending ` , ` completed ` , ` failed ` , ` cancelled ` |
297+ | quoteTxId | string | Quote transaction ID (price lock ID) |
298+ | exchangeRate | string | Exchange rate applied (unit price) |
299+ | fromCoin | string | Source currency/coin |
300+ | fromCoinType | string | Source currency type: ` fiat ` or ` crypto ` |
301+ | toCoin | string | Destination currency/coin |
302+ | toCoinType | string | Destination currency type: ` fiat ` or ` crypto ` |
303+ | fromAmount | string | Source amount (how much user pays) |
304+ | toAmount | string | Destination amount (how much user receives) |
305+ | createdAt | string | Order creation timestamp in ** milliseconds** (13 digits) |
306+ | subUserid | string | Sub-user ID (slave UID) |
307+
308+ ** Status Values:**
309+ - ` pending ` : Order is being processed
310+ - ` completed ` : Order completed successfully
311+ - ` failed ` : Order failed
312+ - ` cancelled ` : Order was cancelled
313+
314+ ** Note** : Unlike payment orders, the ` createdAt ` field in broker prefund orders uses ** milliseconds** (13 digits), not seconds.
0 commit comments