Payment Notify
Authentication
- Share the IP whitelist with each other;
- Bybit Pay will encrypt the callbacks via RSA_SHA256. Partners can provide the public key, sign date put in result head. For details, please see Signature Algorithm
HTTP Request
Method: POST
URL: provide the callback url in the Create Payment request parameter webhookUrl
Header
| Parameter | Comments |
|---|---|
| Content-Type | application/json |
| Version | API version (e.g., "5.00", "5.01"). Matches the version used when creating the order. |
| timestamp | Current timestamp (Unix seconds, 10 digits) |
| publicKey | Merchant's RSA public key (optional, used for webhook signature verification) |
| signature | Generated by Signature Algorithm |
All timestamp fields in webhook callbacks use Unix timestamp in seconds (10 digits):
- Header
timestamp:1736236800(seconds since epoch) - Body fields (
createTime,paymentTime,finishTime):1736233200(seconds since epoch)
Important: When verifying the signature, use the timestamp values exactly as they appear in the JSON body. Do NOT multiply by 1000.
Callback Request Parameters
| Parameter | Type | Comments |
|---|---|---|
| paymentType | string | Payment type E_COMMERCE: Bybit QR Pay for e-commerceE_COMMERCE_REFUND: Bybit QR Pay refund for e-commerce |
| <PayOrderType> | Returned when "paymentType"=E_COMMERCE | |
| <RefundOrderType> | Returned when "paymentType"=E_COMMERCE_REFUND |
Signature Algorithm
Bybit Pay webhook uses JSON body for signature calculation. The signature string is constructed by concatenating:
timestamp + JSON_body
Where:
timestamp: Current Unix timestamp in seconds (10 digits)JSON_body: Complete JSON request body as a string (no sorting, no conversion)
Important: Do NOT convert the JSON to URL-encoded format. Use the raw JSON string directly.
Sign (Bybit Pay Platform)
- Generate current timestamp (Unix seconds):
timestamp = 1736236800 - Marshal the callback data to JSON string (preserve field order as-is)
- Concatenate:
signContent = timestamp + jsonString1736236800{"paymentType":"E_COMMERCE","merchantId":"305142568",...} - Encrypt with SHA256 and sign with RSA (PKCS1v15, 1024-bit key)
- Encode the signature bytes in base64
Verify (Merchant Side)
- Get
timestampandsignaturefrom request headers - Read the raw request body as string (do not parse yet)
- Concatenate:
signContent = timestamp + requestBody1736236800{"paymentType":"E_COMMERCE","merchantId":"305142568",...} - Decode the signature from base64
- Verify the signature using SHA256 and Bybit's RSA public key
Example: Sign & Verify
For example, we sign the following data. The timestamp is 1736236800, the RSA key is
-----The following key pairs are for testing only-----
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAOFSnhqtu40TOtok+yXeB+O76PXb/VAJU4Yih6hViOdSGd7imWmCSZyP
psl3TmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXf+AxlKYMj8OQ
AgDPmZG1a5ydFrje4PLytC7sUw3GP4TTk8xg6iMHmYPdRDv7AEWdAgMBAAE=
-----END RSA PUBLIC KEY-----
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDhUp4arbuNEzraJPsl3gfju+j12/1QCVOGIoeoVYjnUhne4plp
gkmcj6bJd05i4VKAfq082AXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
DI/DkAIAz5mRtWucnRa43XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
AoGBAIqpeCi83516xw32XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
axzIwyfWTTATWbiCS9sqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
6gOQYu8kzRqCzqvyMNdAHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
e/Gxi7qhuIKz0mvfA/yieXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3IBIdV/CbwJBAOQcsOPf+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
/5V9zdim+hPq+9cvsqO7dXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
MY4uV8noiqDRf/pvAkyMMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
15eWR4jEoXMIFkd7Onc6tXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Bnj6KW1fk+UM29dUDjmTqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
IavMyjrhDKyBGZ0mI6eoREaC4bxl31RRkYtg9mNeU3TxsBM=
-----END RSA PRIVATE KEY-----
JSON Payload Example:
{
"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
}
Signature Calculation Steps:
- Generate timestamp:
1736236800 - Convert JSON to string (minified, no extra spaces):
{"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} - Concatenate:
signContent = timestamp + jsonString1736236800{"paymentType":"E_COMMERCE","merchantId":"305142568",...} - Sign with SHA256 + RSA private key
- Encode to base64:
NgDZLZCVBdma904hzZXmU+fQ7dr7z7muZkuwAbDnibLXXXXXXXXXXXXgmzad58LfRtLXGlkNPXXXXXXXXXXX9jNYd6gxp7j0Mlh0vQlQCIb2283DQ3wbZDphilvXXXXXXXXXXXXX2IIBelYBBw39U=
Verification Steps (Merchant Side):
- Extract
timestampandsignaturefrom request headers - Read raw request body (string, do not parse)
- Concatenate:
signContent = timestamp + requestBody - Decode signature from base64
- Verify using SHA256 + RSA public key
Test Keys:
-----The following key pairs are for testing only-----
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAOFSnhqtu40TOtok+yXeB+O76PXb/VAJU4Yih6hViOdSGd7imWmCSZyP
psl3TmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXf+AxlKYMj8OQ
AgDPmZG1a5ydFrje4PLytC7sUw3GP4TTk8xg6iMHmYPdRDv7AEWdAgMBAAE=
-----END RSA PUBLIC KEY-----
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDhUp4arbuNEzraJPsl3gfju+j12/1QCVOGIoeoVYjnUhne4plp
gkmcj6bJd05i4VKAfq082AXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
DI/DkAIAz5mRtWucnRa43XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
AoGBAIqpeCi83516xw32XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
axzIwyfWTTATWbiCS9sqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
6gOQYu8kzRqCzqvyMNdAHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
e/Gxi7qhuIKz0mvfA/yieXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3IBIdV/CbwJBAOQcsOPf+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
/5V9zdim+hPq+9cvsqO7dXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
MY4uV8noiqDRf/pvAkyMMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
15eWR4jEoXMIFkd7Onc6tXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Bnj6KW1fk+UM29dUDjmTqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
IavMyjrhDKyBGZ0mI6eoREaC4bxl31RRkYtg9mNeU3TxsBM=
-----END RSA PRIVATE KEY-----
Request Example
Callback Pay Order (Payment Success)
POST ${webhook url} HTTP/1.1
Host: www.merchant.com
timestamp: 1736233260
signature: NgDZLZCVBdma904hzZXmU+fQ7dr7z7muZkuwAbDnibLXXXXXXXXXXXXgmzad58Lf...
Content-Type: application/json
{
"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,
"customer": {
"uid": "104326789",
"externalUserId": "user123@merchant.com",
"userName": "John Doe",
"registerTime": 1704067200,
"kycCountry": "USA"
}
}
Callback Pay Order (Payment Failed)
POST ${webhook url} HTTP/1.1
Host: www.merchant.com
timestamp: 1736233500
signature: NgDZLZCVBdma904hzZXmU+fQ7dr7z7muZkuwAbDnibLXXXXXXXXXXXXgmzad58Lf...
Content-Type: application/json
{
"paymentType": "E_COMMERCE",
"merchantId": "305142568",
"clientId": "client_001",
"merchantTradeNo": "bf9d3e2-6c4f-5b0g-c8d9-9e3f2g4b6d7e",
"payId": "01JY2KM5QNPXR8S4HTJZT9BC13",
"status": "PAY_FAILED",
"amount": "50",
"currency": "USDT",
"currencyType": "crypto",
"createTime": 1736233300,
"paymentTime": 0,
"finishTime": 1736233500,
"customer": {
"uid": "104326790",
"externalUserId": "user456@merchant.com",
"userName": "Jane Smith",
"registerTime": 1709251200,
"kycCountry": "GBR"
}
}
Callback Refund Order
POST ${webhook url} HTTP/1.1
Host: www.merchant.com
timestamp: 1736234000
signature: NgDZLZCVBdma904hzZXmU+fQ7dr7z7muZkuwAbDnibLXXXXXXXXXXXXgmzad58Lf...
Content-Type: application/json
{
"paymentType": "E_COMMERCE_REFUND",
"refundId": "RF01JY2KM5QNPXR8S4HTJZT9BC14",
"refundType": "MERCHNT_SELF_REFUND",
"merchantTradeNo": "af8c2d1-5b3e-4a9f-b6c7-8d2e1f3a4b5c",
"merchantRefundNo": "RF-2026-0001",
"payId": "01JY2KM5QNPXR8S4HTJZT9BC12",
"refundStatus": "REFUND_SUCCESS",
"refundCurrency": "USDT",
"amount": "50",
"createTime": 1736234000
}
Callback Broker Prefund Order
This webhook is triggered for broker prefund orders (payment type: BROKER_PREFUND). It notifies merchants about cryptocurrency exchange/conversion order status updates.
Use Case: When a merchant processes a fiat-to-crypto or crypto-to-fiat conversion through Bybit's broker service.
POST ${webhook url} HTTP/1.1
Host: www.merchant.com
Version: 5.01
timestamp: 1736235000
signature: NgDZLZCVBdma904hzZXmU+fQ7dr7z7muZkuwAbDnibLXXXXXXXXXXXXgmzad58Lf...
Content-Type: application/json
{
"tradeNo": "DL202601280001",
"status": "completed",
"quoteTxId": "QUOTE-20260128-001",
"exchangeRate": "1.0025",
"fromCoin": "USD",
"fromCoinType": "fiat",
"toCoin": "USDT",
"toCoinType": "crypto",
"fromAmount": "1000.00",
"toAmount": "998.75",
"createdAt": "1736235000000",
"subUserid": "12345678"
}
Broker Prefund Order Fields:
| Field | Type | Description |
|---|---|---|
| tradeNo | string | Broker deal order ID |
| status | string | Order status: pending, completed, failed, cancelled |
| quoteTxId | string | Quote transaction ID (price lock ID) |
| exchangeRate | string | Exchange rate applied (unit price) |
| fromCoin | string | Source currency/coin |
| fromCoinType | string | Source currency type: fiat or crypto |
| toCoin | string | Destination currency/coin |
| toCoinType | string | Destination currency type: fiat or crypto |
| fromAmount | string | Source amount (how much user pays) |
| toAmount | string | Destination amount (how much user receives) |
| createdAt | string | Order creation timestamp in milliseconds (13 digits) |
| subUserid | string | Sub-user ID (slave UID) |
Status Values:
pending: Order is being processedcompleted: Order completed successfullyfailed: Order failedcancelled: Order was cancelled
Note: Unlike payment orders, the createdAt field in broker prefund orders uses milliseconds (13 digits), not seconds.