Skip to Content
ResourcesBest PracticesSecurity Best Practices

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:

  • transactionId returned 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******1111

Log 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 .gitignore includes configuration files
  • Limit production key access permissions
  • Configure Webhook URL as HTTPS
Last updated on