Request Signature and Verification Guide
To ensure the authenticity and integrity of requests during transmission, RedotPay uses asymmetric encryption algorithms for digital signatures on API requests. This guide details how to generate signatures for API requests sent to RedotPay and how to verify signatures for callback notifications received from RedotPay.
Signature Scope
In Production and Gray environments, all API requests containing body parameters must be signed using the SHA256withRSA algorithm. Signature information is transmitted through HTTP request headers.
Key Management
Key Generation and Configuration
-
Generate Key Pair: Use OpenSSL or other tools to generate an RSA key pair (recommended 2048-bit)
# Generate private key openssl genrsa -out private_key.pem 2048 # Generate public key from private key openssl rsa -in private_key.pem -pubout -out public_key.pem -
Configure Keys:
- Upload your public key to the RedotPay merchant platform
- Obtain RedotPay's public key from the RedotPay platform for callback signature verification
- The private key must be securely stored on your server and never leaked
Key Version Management
Use the X-R-Key-Version header to specify the key version, using natural numbers (e.g., 1, 2, 3...):
1: Initial key version2: Second rotation key (when updating keys)3: Third rotation key- And so on...
Generating Request Signatures
Step 1: Prepare Signature Elements
- Determine the key version and load the corresponding private key
- Prepare the following signature elements:
http-method: HTTP request method (e.g.,POST, must be uppercase)http-uri: API URI path (e.g.,/openapi/v2/order/create)appKey: Your merchant identifiertimestamp: Current Unix timestamp (milliseconds)requestBody: JSON format request body string (compact format, no extra spaces)
Step 2: Construct the String to Sign
String to sign (stringToSign) format:
{http-method} {http-uri}\n{appKey}.{timestamp}.{requestBody}
Note: There is a space between {http-method} and {http-uri}.
Example:
POST /openapi/v2/order/create
4CA7B705-8EF5-4AC3-A0B6-9A4B84EF13B6.1763555087656.{"amount":100,"currency":"USD"}
Step 3: Generate Signature Using SHA256withRSA
Use your RSA private key to sign stringToSign, then perform Base64 encoding and URL encoding.
Signature generation formula:
signature = base64Encode(sha256withRSA(stringToSign, privateKey))
Java Code Example:
import java.security.*;
import java.util.Base64;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class RsaSignatureGenerator {
public static String generateSignature(String httpMethod, String httpUri,
String appKey, PrivateKey privateKey,
long timestamp, String requestBody) throws Exception {
// 1. Construct the string to sign
String stringToSign = String.format("%s %s\n%s.%d.%s",
httpMethod, httpUri, appKey, timestamp, requestBody);
// 2. Sign using SHA256withRSA
Signature privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(privateKey);
privateSignature.update(stringToSign.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = privateSignature.sign();
// 3. Base64 encode and URL encode
String base64Signature = Base64.getEncoder().encodeToString(signatureBytes);
return base64Signature;
}
// Load private key
public static PrivateKey loadPrivateKey(String keyPath) throws Exception {
String privateKeyPEM = new String(java.nio.file.Files.readAllBytes(
java.nio.file.Paths.get(keyPath)));
privateKeyPEM = privateKeyPEM
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(privateKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decoded));
}
}
Python Code Example:
import base64
import urllib.parse
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
import time
def generate_signature(http_method: str, http_uri: str,
app_key: str, private_key_path: str,
request_body: str) -> tuple:
"""
Generate RSA signature
:return: (timestamp, signature, key_version) tuple
"""
# 1. Get timestamp and key version
timestamp = int(time.time() * 1000)
key_version = 1 # Adjust according to the actual key version used
# 2. Construct the string to sign
string_to_sign = f"{http_method} {http_uri}\n{app_key}.{timestamp}.{request_body}"
# 3. Load private key
with open(private_key_path, "rb") as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None,
backend=default_backend()
)
# 4. Sign using SHA256
signature_bytes = private_key.sign(
string_to_sign.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA256()
)
# 5. Base64 encode and URL encode
return base64.b64encode(signature_bytes).decode('utf-8')
Step 4: Send the Request with Signature
Add the generated signature to the HTTP request headers:
Complete Request Headers Example:
POST /openapi/v2/order/create HTTP/1.1
Host: api.redotpay.com
Content-Type: application/json
X-Merchant-Ak: 4CA7B705-8EF5-4AC3-A0B6-9A4B84EF13B6
X-R-Ts: 1763555087656
X-R-Key-Version: 1
X-R-Signature: URL-encoded Base64 signature string
{"amount":100,"currency":"USD"}
Verifying RedotPay Callback Notification Signatures
Callback Notification Signature Rules
Callback notification string to sign format:
{appKey}.{timestamp}.{requestBody}
Note: Callback notification signatures do not include http-method and http-uri.
Verification Steps
-
Extract elements: From callback request headers:
X-R-Ts: TimestampX-R-Signature: URL-encoded Base64 signatureX-R-Key-Version: Key version (natural number)
-
Decode signature: URL decode and Base64 decode the signature
-
Construct verification string: Format as
{appKey}.{timestamp}.{requestBody} -
Obtain RedotPay public key: Get corresponding RedotPay public key based on
X-R-Key-Version -
Verify signature: Use RedotPay public key to verify the signature
Java Verification Example:
import java.security.*;
import java.util.Base64;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
public class CallbackVerifier {
public static boolean verifyCallback(String appKey, String timestamp,
String requestBody, String encodedSignature,
int keyVersion, PublicKey redotpayPublicKey) throws Exception {
// 1. Decode signature
String decodedSignature = URLDecoder.decode(encodedSignature, StandardCharsets.UTF_8.toString());
byte[] signatureBytes = Base64.getDecoder().decode(decodedSignature);
// 2. Construct verification string
String stringToVerify = String.format("%s.%s.%s", appKey, timestamp, requestBody);
// 3. Verify signature
Signature publicSignature = Signature.getInstance("SHA256withRSA");
publicSignature.initVerify(redotpayPublicKey);
publicSignature.update(stringToVerify.getBytes(StandardCharsets.UTF_8));
return publicSignature.verify(signatureBytes);
}
// Load RedotPay public key
public static PublicKey loadRedotpayPublicKey(int keyVersion) throws Exception {
// Get corresponding RedotPay public key based on keyVersion
// This should obtain the public key string from secure storage or configuration
String publicKeyPEM = getPublicKeyByVersion(keyVersion);
publicKeyPEM = publicKeyPEM
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(publicKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(new X509EncodedKeySpec(decoded));
}
// Get public key by version
private static String getPublicKeyByVersion(int version) {
switch (version) {
case 1:
return "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."; // Version 1 public key
case 2:
return "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."; // Version 2 public key
default:
throw new IllegalArgumentException("Unsupported key version: " + version);
}
}
}
Python Verification Example:
import base64
import urllib.parse
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
def verify_callback_signature(headers: dict, request_body: str,
app_key: str, expected_key_version: int) -> bool:
"""
Verify callback signature
:param headers: Request headers containing X-R-Ts, X-R-Signature, X-R-Key-Version
:param request_body: Original request body
:param app_key: Merchant appKey
:param expected_key_version: Expected key version (natural number)
:return: Verification result
"""
try:
# 1. Extract parameters from headers
timestamp = headers.get('X-R-Ts')
encoded_signature = headers.get('X-R-Signature')
received_key_version = headers.get('X-R-Key-Version')
# 2. Check key version
if not received_key_version or int(received_key_version) != expected_key_version:
return False
# 3. Get corresponding RedotPay public key
public_key = get_redotpay_public_key(int(received_key_version))
# 4. Decode signature
decoded_signature = urllib.parse.unquote(encoded_signature)
signature_bytes = base64.b64decode(decoded_signature)
# 5. Construct verification string
string_to_verify = f"{app_key}.{timestamp}.{request_body}"
# 6. Verify signature
public_key.verify(
signature_bytes,
string_to_verify.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except (InvalidSignature, ValueError, KeyError, TypeError):
return False
def get_redotpay_public_key(key_version: int):
"""
Get RedotPay public key based on key version
:param key_version: Key version (natural number)
"""
# Get corresponding public key file path based on version number
key_file_path = f"redotpay_public_key_v{key_version}.pem"
with open(key_file_path, "rb") as key_file:
return serialization.load_pem_public_key(
key_file.read(),
backend=default_backend()
)
Key Considerations
1. Algorithm Requirements
- Must use SHA256withRSA algorithm
- Recommended key length 2048-bit or higher
- Perform SHA256 hash calculation before signing
2. Key Security Management
- Private key: Must be securely stored on the server, recommended to use HSM (Hardware Security Module) or key management service
- Public key exchange:
- Upload your public key to the RedotPay merchant platform
- Obtain and securely store RedotPay's public key from the RedotPay platform
- Key rotation:
- Regularly rotate keys (recommended every 12 months)
- Upload new public key first, then update the key version number in code during rotation
- Keep old keys available for a period to ensure smooth transition
3. Timestamp Verification
- Timestamp deviation should be within ±5 minutes
- Prevent replay attacks by recording processed timestamps
4. Error Handling
try {
// Signature or verification logic
} catch (InvalidKeyException e) {
// Invalid key, check key format or version
} catch (SignatureException e) {
// Signature verification failed
} catch (InvalidAlgorithmParameterException e) {
// Algorithm parameter error
} catch (NumberFormatException e) {
// Key version number format error, cannot convert to natural number
}
Frequently Asked Questions
Q1: Possible reasons for signature verification failure?
- Incorrect string to sign format (check line breaks and punctuation)
- Timestamp outside allowed range
- Key version mismatch
- Public/private key mismatch
- Request body JSON format changes (spaces, line breaks, etc.)
Q2: How to test signature implementation?
- Use test environment key pairs
- Record generated string to sign
- Verify signature using OpenSSL command line
- Compare with RedotPay's example code
Q3: Precautions during key rotation?
- Ensure both parties have completed configuration before new key takes effect
- Update key version number in code (natural number increment)
- Monitor error rates during old and new key transition period
- Keep old keys available for at least 7 days before disabling
Signature Example Checklist
Ensure your implementation includes all the following elements:
- Use correct string to sign format
- Include
X-R-Key-Versionrequest header with natural number value - Use SHA256withRSA algorithm
- Base64 and URL encode the signature
- Use correct string format for callback verification (without http-method and uri)
- Use millisecond Unix timestamp
- Use compact JSON format for request body
- Use natural numbers for key version (1, 2, 3...)
By following this guide, you can ensure secure and reliable integration with RedotPay API. For any issues, please refer to the latest documentation on the RedotPay merchant platform or contact technical support.
Updated about 2 months ago
