Security Best Practices
This document introduces security best practices that merchants should follow when integrating SUNBAY API.
API Key Management
Secure Storage
Never Hardcode API Keys
Never write API keys directly in code or commit them to version control systems (like Git).
✅ Recommended Approach: Use Environment Variables
// Read from environment variables
String apiKey = System.getenv("SUNBAY_API_KEY");
String apiSecret = System.getenv("SUNBAY_API_SECRET");
NexusClient client = new NexusClient.Builder()
.apiKey(apiKey)
.apiSecret(apiSecret)
.build();# Set environment variables
export SUNBAY_API_KEY=your_api_key
export SUNBAY_API_SECRET=your_api_secret✅ Recommended Approach: Use Configuration Files (Don’t Commit to Git)
# application.yml (add to .gitignore)
sunbay:
api:
key: ${SUNBAY_API_KEY}
secret: ${SUNBAY_API_SECRET}@Value("${sunbay.api.key}")
private String apiKey;
@Value("${sunbay.api.secret}")
private String apiSecret;Key Protection
- ✅ Use different keys for testing and production environments
- ✅ Limit key access permissions (only necessary personnel can access)
- ✅ Regularly check code to ensure no key leakage
- ❌ Don’t print complete API keys in logs
HTTPS Usage
Enforce HTTPS
All communication with SUNBAY API must use HTTPS; SDK enforces HTTPS by default.
If you call API directly (without using SDK), ensure URL starts with https://.
// ✅ Correct
String baseUrl = "https://api.sunbay.com";
// ❌ Wrong - Don't use HTTP
String baseUrl = "http://api.sunbay.com";Sensitive Information Handling
Don’t Store Sensitive Data
Prohibited from Storing the Following Sensitive Information
- Complete bank card number (PAN)
- CVV/CVC security code
- PIN code
- Magnetic stripe data
You Can Store:
transactionIdreturned by SUNBAY (Transaction ID)- Masked card number (e.g.,
411111******1111) - Transaction status and amount
Data Masking
If you need to display card numbers, they must be masked:
public class CardMasker {
/**
* Mask card number - only show first 6 and last 4 digits
*/
public static String maskCardNumber(String cardNumber) {
if (cardNumber == null || cardNumber.length() < 10) {
return "****";
}
return cardNumber.substring(0, 6) + "******" +
cardNumber.substring(cardNumber.length() - 4);
}
}
// Usage example
String maskedCard = CardMasker.maskCardNumber("4111111111111111");
// Output: 411111******1111Log Masking
Don’t record sensitive information in logs:
// ❌ Wrong - Don't log complete card number
log.info("Processing payment for card: {}", cardNumber);
// ✅ Correct - Log masked card number
log.info("Processing payment for card: {}", CardMasker.maskCardNumber(cardNumber));
// ✅ Correct - Only log transaction ID
log.info("Processing payment. TransactionId: {}", transactionId);Webhook Signature Verification
When receiving Webhook notifications, you must verify the signature to ensure the request is from SUNBAY.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class WebhookValidator {
private final String webhookSecret;
/**
* Verify Webhook signature
*/
public boolean verifySignature(String payload, String signature, String timestamp) {
try {
// 1. Verify timestamp (prevent replay attacks)
long requestTime = Long.parseLong(timestamp);
long currentTime = System.currentTimeMillis() / 1000;
if (Math.abs(currentTime - requestTime) > 300) { // 5 minutes
log.warn("Webhook timestamp expired");
return false;
}
// 2. Construct signature string
String signatureString = timestamp + "." + payload;
// 3. Calculate signature
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
webhookSecret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(secretKey);
byte[] hash = mac.doFinal(signatureString.getBytes(StandardCharsets.UTF_8));
// 4. Convert to hexadecimal
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
String expectedSignature = hexString.toString();
// 5. Compare signatures
return MessageDigest.isEqual(
signature.getBytes(StandardCharsets.UTF_8),
expectedSignature.getBytes(StandardCharsets.UTF_8)
);
} catch (Exception e) {
log.error("Failed to verify webhook signature", e);
return false;
}
}
}Usage Example:
@PostMapping("/webhook/payment")
public ResponseEntity<String> handleWebhook(
@RequestBody String payload,
@RequestHeader("X-SUNBAY-Signature") String signature,
@RequestHeader("X-SUNBAY-Timestamp") String timestamp) {
// Verify signature
if (!webhookValidator.verifySignature(payload, signature, timestamp)) {
log.error("Invalid webhook signature");
return ResponseEntity.status(401).body("Invalid signature");
}
// Process event
processWebhookEvent(payload);
return ResponseEntity.ok("OK");
}Security Checklist
Development Phase
- API keys stored using environment variables
- No hardcoded keys in code
- All API calls use HTTPS
- Don’t store complete card number, CVV, PIN
- Logs don’t contain sensitive information
- Implement Webhook signature verification
Testing Phase
- Use test environment keys for testing
- Verify Webhook signature verification logic
- Check log output to ensure no sensitive information
Production Phase
- Use production environment API keys
- Confirm
.gitignoreincludes configuration files - Limit production key access permissions
- Configure Webhook URL as HTTPS