Signature Algorithm
Overview
All Agreement Payment APIs use the same authentication mechanism as QR Payment: HMAC_SHA256 or RSA_SHA256 signature via standard Bybit API headers.
Signature Algorithm
| Algorithm | Key Type | Signature Header | Description |
|---|---|---|---|
| HMAC_SHA256 | API Secret (symmetric) | X-BAPI-SIGN (hex) | System-generated API key |
| RSA_SHA256 | RSA private key (asymmetric) | X-BAPI-SIGN (base64) | Self-generated API key |
Reference: See Bybit API authentication examples for complete sample code.
Request Signature
Signature Flow
1. Construct String to Sign
# POST request:
String to sign = timestamp + api_key + recv_window + raw_request_body
# GET request:
String to sign = timestamp + api_key + recv_window + queryString
Example (POST):
1736233200000xxxxxxxxxxxxxxxxxx5000{"merchant_id":"M123456789","user_id":"U_123456789",...}
Example (GET):
1736233200000xxxxxxxxxxxxxxxxxx5000merchant_id=M123456789&user_id=U_123456789&agreement_type=CYCLE&agreement_no=AGR202601070001
2. Calculate Signature
# HMAC_SHA256 (System-generated key):
Signature = HEX(HMAC_SHA256(String to sign, api_secret))
# RSA_SHA256 (Self-generated key):
Signature = Base64(RSA_SHA256_Sign(String to sign, merchant_private_key))
3. Set Request Headers
X-BAPI-API-KEY: xxxxxxxxxxxxxxxxxx
X-BAPI-TIMESTAMP: 1736233200000
X-BAPI-SIGN: {calculated signature}
X-BAPI-RECV-WINDOW: 5000
Code Examples
Java (HMAC_SHA256)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.HexFormat;
public class SignatureUtil {
public static String signHmac(String timestamp, String apiKey,
String recvWindow, String body,
String apiSecret) throws Exception {
String content = timestamp + apiKey + recvWindow + body;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(apiSecret.getBytes("UTF-8"), "HmacSHA256"));
byte[] hash = mac.doFinal(content.getBytes("UTF-8"));
return HexFormat.of().formatHex(hash);
}
}
Python (HMAC_SHA256)
import hmac, hashlib
def sign_request(timestamp, api_key, recv_window, body, api_secret):
content = f"{timestamp}{api_key}{recv_window}{body}"
return hmac.new(
api_secret.encode('utf-8'),
content.encode('utf-8'),
hashlib.sha256
).hexdigest()
cURL Example
#!/bin/bash
API_HOST="https://api2.bybit.com"
API_PATH="/v5/bybitpay/agreement/pay"
API_KEY="xxxxxxxxxxxxxxxxxx"
API_SECRET="your_api_secret"
RECV_WINDOW="5000"
REQUEST_BODY='{"merchant_id":"M123456789","user_id":"U_123456789","agreement_type":"CYCLE","agreement_no":"AGR202601070001","out_trade_no":"ORDER20260107001","scene_code":"SUBSCRIPTION","amount":{"total":"2350","currency":"USDT","currency_type":"CRYPTO","chain":"TRC20"},"order_info":{"order_title":"Monthly subscription"},"notify_url":"https://merchant.com/notify/pay"}'
TIMESTAMP=$(date +%s000)
SIGN_CONTENT="${TIMESTAMP}${API_KEY}${RECV_WINDOW}${REQUEST_BODY}"
SIGNATURE=$(echo -n "$SIGN_CONTENT" | openssl dgst -sha256 -hmac "$API_SECRET" | awk '{print $2}')
curl -X POST "${API_HOST}${API_PATH}" \
-H "Content-Type: application/json" \
-H "X-BAPI-API-KEY: ${API_KEY}" \
-H "X-BAPI-TIMESTAMP: ${TIMESTAMP}" \
-H "X-BAPI-SIGN: ${SIGNATURE}" \
-H "X-BAPI-RECV-WINDOW: ${RECV_WINDOW}" \
-d "${REQUEST_BODY}"
Webhook Signature Verification
Verification Steps
- Get
signandsignTypefrom request body - Remove
signandsignTypeto get content to verify - Use platform public key to verify signature (RSA2/SHA256withRSA)
- Verify timestamp from
X-Timestampheader (within 5 minutes)
Java Example
public class WebhookVerifier {
public boolean verifyWebhook(String body, String timestamp,
String platformPublicKey) throws Exception {
// 1. Check timestamp
long now = System.currentTimeMillis();
long ts = Long.parseLong(timestamp);
if (Math.abs(now - ts) > 5 * 60 * 1000) {
return false;
}
// 2. Extract signature
JSONObject notify = JSONObject.parseObject(body);
String sign = notify.getString("sign");
// 3. Remove sign and signType
notify.remove("sign");
notify.remove("signType");
String contentToVerify = notify.toJSONString();
// 4. Verify RSA2 signature
return verifyRSA2(contentToVerify, sign, platformPublicKey);
}
private boolean verifyRSA2(String content, String signBase64,
String publicKeyStr) throws Exception {
String keyContent = publicKeyStr
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
byte[] keyBytes = Base64.getDecoder().decode(keyContent);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(content.getBytes("UTF-8"));
return signature.verify(Base64.getDecoder().decode(signBase64));
}
}
Key Management
| Key Type | Purpose | Custodian | Format |
|---|---|---|---|
| Merchant Private Key | Sign API requests | Merchant (confidential) | PEM (PKCS1/PKCS8) |
| Merchant Public Key | Platform verifies requests | Platform | PEM (X.509) |
| Platform Private Key | Sign webhooks | Platform | PEM |
| Platform Public Key | Verify webhooks | Merchant | PEM (X.509) |
Private Key Formats
# PKCS1 format
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----
# PKCS8 format (recommended)
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkq...
-----END PRIVATE KEY-----
Security Requirements
- Store private keys encrypted; never store in plaintext
- Rotate keys periodically (recommended annually)
- Contact platform immediately if key is compromised
- Use different keys for test and production environments
- PKCS8 format is recommended for better compatibility