Security Best Practices
Security is critical when integrating payment systems. This guide covers best practices for securing your DVPay API integration, protecting customer data, and preventing common security vulnerabilities.
API Credential Security
Store Credentials Securely
✅ Do: Use Environment Variables
# Store credentials in environment variables
DVPAY_APP_ID=your_app_id_here
DVPAY_API_KEY=your_api_key_here
DVPAY_WEBHOOK_SECRET=your_webhook_secret_here
# Never commit .env to git
# Add to .gitignore
import "os"
type Config struct {
AppID string
APIKey string
}
func LoadConfig() *Config {
return &Config{
AppID: os.Getenv("DVPAY_APP_ID"),
APIKey: os.Getenv("DVPAY_API_KEY"),
}
}
require('dotenv').config();
const config = {
appId: process.env.DVPAY_APP_ID,
apiKey: process.env.DVPAY_API_KEY,
webhookSecret: process.env.DVPAY_WEBHOOK_SECRET
};
module.exports = config;
❌ Don't: Hardcode Credentials
// NEVER DO THIS!
const apiKey = 'sk_live_abc123xyz789'; // Exposed in code
const appId = 'app_456def'; // Version control leak risk
Credential Rotation
Rotate API keys regularly to minimize exposure risk:
Generate New Credentials
In DVPay merchant dashboard: API Settings → Regenerate Keys
Update Environment Variables
# Update production environment
export DVPAY_API_KEY=new_key_value
Deploy Updated Configuration
Redeploy your application with new credentials
Revoke Old Credentials
In DVPay dashboard: API Settings → Revoke Old Keys
HTTPS & Transport Security
Always Use HTTPS
✅ Correct:
const baseURL = 'https://merchant.dv.vai247.pro/api/v1'; // HTTPS
❌ Wrong:
const baseURL = 'http://merchant.dv.vai247.pro/api/v1'; // HTTP (insecure!)
Verify SSL Certificates
Ensure your HTTP client validates SSL certificates:
import (
"crypto/tls"
"net/http"
)
func createSecureClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12, // Enforce TLS 1.2+
// InsecureSkipVerify: false, // NEVER set to true in production
},
},
}
}
const https = require('https');
const axios = require('axios');
const client = axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: true, // Verify SSL certificates
minVersion: 'TLSv1.2' // Enforce TLS 1.2+
})
});
Webhook Security
Verify Webhook Signatures
Always validate that webhooks are sent by DVPay:
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func verifyWebhookSignature(payload []byte, signature string, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expectedSignature := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-DVPay-Signature")
webhookSecret := os.Getenv("DVPAY_WEBHOOK_SECRET")
if !verifyWebhookSignature(body, signature, webhookSecret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Process webhook...
}
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhooks/dvpay', (req, res) => {
const signature = req.headers['x-dvpay-signature'];
const webhookSecret = process.env.DVPAY_WEBHOOK_SECRET;
if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook...
res.status(200).json({ status: 'received' });
});
Use HTTPS Webhook Endpoints
# ✅ Correct
https://yourdomain.com/webhooks/dvpay
# ❌ Wrong
http://yourdomain.com/webhooks/dvpay
DVPay will reject HTTP webhook URLs in production.
Request Validation
Validate Timestamp Headers
Prevent replay attacks by validating timestamps:
func validateTimestamp(timestamp string) error {
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return errors.New("invalid timestamp format")
}
now := time.Now().Unix()
diff := now - ts
// Allow ±5 minutes tolerance
if diff > 300 || diff < -300 {
return errors.New("timestamp expired")
}
return nil
}
func makeAPIRequest(url string, payload interface{}) error {
timestamp := fmt.Sprintf("%d", time.Now().Unix())
if err := validateTimestamp(timestamp); err != nil {
return err
}
req.Header.Set("X-Timestamp", timestamp)
// Continue with request...
}
function validateTimestamp(timestamp) {
const now = Math.floor(Date.now() / 1000);
const diff = Math.abs(now - parseInt(timestamp));
// Allow ±5 minutes tolerance
if (diff > 300) {
throw new Error('Timestamp expired');
}
return true;
}
function createAuthHeaders() {
const timestamp = Math.floor(Date.now() / 1000).toString();
validateTimestamp(timestamp);
return {
'X-App-Id': config.appId,
'X-Api-Key': config.apiKey,
'X-Timestamp': timestamp
};
}
Sanitize User Input
Always validate and sanitize user-provided data:
import (
"errors"
"regexp"
)
func validateOrderRequest(req CreateOrderRequest) error {
// Validate amount
if req.Amount <= 0 {
return errors.New("amount must be positive")
}
if req.Amount > 10000 {
return errors.New("amount exceeds maximum")
}
// Validate currency
validCurrencies := map[string]bool{"USD": true, "KHR": true}
if !validCurrencies[req.Currency] {
return errors.New("invalid currency")
}
// Validate store ID format
matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]+$`, req.StoreID)
if !matched {
return errors.New("invalid store_id format")
}
return nil
}
function validateOrderRequest(orderData) {
const errors = [];
// Validate amount
if (typeof orderData.amount !== 'number' || orderData.amount <= 0) {
errors.push('Amount must be a positive number');
}
if (orderData.amount > 10000) {
errors.push('Amount exceeds maximum limit');
}
// Validate currency
const validCurrencies = ['USD', 'KHR'];
if (!validCurrencies.includes(orderData.currency)) {
errors.push('Invalid currency code');
}
// Validate store_id format
if (!/^[a-zA-Z0-9_-]+$/.test(orderData.store_id)) {
errors.push('Invalid store_id format');
}
if (errors.length > 0) {
throw new Error(errors.join(', '));
}
return true;
}
Data Protection
Minimize Data Exposure
Only log necessary information and never log sensitive data:
✅ Safe Logging:
logger.info('Order created', {
order_id: order.id,
amount: order.amount,
currency: order.currency,
timestamp: Date.now()
});
❌ Unsafe Logging:
// NEVER log sensitive data!
logger.info('Request sent', {
api_key: config.apiKey, // ❌ Exposed credential
customer_phone: customer.phone, // ❌ PII
full_request: request // ❌ May contain secrets
});
Mask Sensitive Data
When displaying data to users or in logs:
function maskApiKey(apiKey) {
return apiKey.substring(0, 8) + '****' + apiKey.substring(apiKey.length - 4);
}
console.log(`Using API key: ${maskApiKey(config.apiKey)}`);
// Output: Using API key: sk_live_****xyz9
Rate Limiting & Abuse Prevention
Implement Client-Side Rate Limiting
Prevent hitting API rate limits:
import (
"sync"
"time"
)
type RateLimiter struct {
requests int
limit int
window time.Duration
mu sync.Mutex
reset time.Time
}
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
return &RateLimiter{
limit: limit,
window: window,
reset: time.Now().Add(window),
}
}
func (rl *RateLimiter) Allow() bool {
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
if now.After(rl.reset) {
rl.requests = 0
rl.reset = now.Add(rl.window)
}
if rl.requests >= rl.limit {
return false
}
rl.requests++
return true
}
// Usage
limiter := NewRateLimiter(100, time.Minute)
if !limiter.Allow() {
return errors.New("rate limit exceeded")
}
class RateLimiter {
constructor(limit, windowMs) {
this.limit = limit;
this.windowMs = windowMs;
this.requests = 0;
this.resetTime = Date.now() + windowMs;
}
allow() {
const now = Date.now();
if (now > this.resetTime) {
this.requests = 0;
this.resetTime = now + this.windowMs;
}
if (this.requests >= this.limit) {
return false;
}
this.requests++;
return true;
}
}
// Usage: 100 requests per minute
const limiter = new RateLimiter(100, 60000);
if (!limiter.allow()) {
throw new Error('Rate limit exceeded');
}
Implement Idempotency
Prevent duplicate operations:
const { v4: uuidv4 } = require('uuid');
// Store idempotency key with request
const idempotencyKey = uuidv4();
await axios.post(url, orderData, {
headers: {
...authHeaders,
'X-Idempotency-Key': idempotencyKey
}
});
// Same key returns same result if retried
IP Whitelisting
Restrict API access to known IP addresses:
Get Your Server IPs
curl ifconfig.me
# Output: 203.0.113.10
Configure in DVPay Dashboard
API Settings → IP Whitelist → Add your server IPs
Test Access
Requests from non-whitelisted IPs will return 403 Forbidden
Dependency Security
Keep Dependencies Updated
Regularly update packages to patch vulnerabilities:
# Check for vulnerabilities
npm audit
# Update dependencies
npm update
# For critical vulnerabilities
npm audit fix
Use Dependency Scanning
Add security scanning to your CI/CD pipeline:
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run npm audit
run: npm audit --audit-level=moderate
Error Handling Security
Don't Expose Internal Details
✅ Safe Error Response:
catch (error) {
res.status(500).json({
error: 'An error occurred processing your request',
request_id: error.request_id
});
// Log full details internally
logger.error('Internal error', { error, stack: error.stack });
}
❌ Unsafe Error Response:
catch (error) {
// ❌ Exposes internal details
res.status(500).json({
error: error.message,
stack: error.stack, // ❌ Reveals code structure
config: config // ❌ May contain secrets
});
}
Security Checklist
Before going to production:
Credentials
- API keys stored in environment variables
- Credentials not committed to version control
- Separate sandbox and production credentials
- Credential rotation schedule established
Transport Security
- All requests use HTTPS
- SSL certificate validation enabled
- TLS 1.2+ enforced
Webhook Security
- Webhook signature verification implemented
- HTTPS webhook endpoint configured
- Idempotent webhook processing
Validation
- Timestamp validation implemented
- User input sanitized
- Request/response validation
Data Protection
- Sensitive data not logged
- API keys masked in logs
- PII handling compliant with regulations
Rate Limiting
- Client-side rate limiting implemented
- Retry logic with exponential backoff
- Idempotency keys used for operations
Monitoring
- Error logging with request IDs
- Security alerts configured
- Failed authentication monitoring
Compliance Considerations
PCI DSS Compliance
DVPay handles payment processing, but you must:
- Never store card numbers, CVV, or full track data
- Use HTTPS for all payment-related pages
- Maintain security logs and monitoring
- Implement proper access controls
GDPR & Data Privacy
When handling customer data:
- Collect only necessary information
- Store data securely
- Provide data deletion capabilities
- Maintain data processing records
Incident Response
If credentials are compromised:
Immediately Revoke
Revoke compromised API keys in DVPay dashboard
Generate New Credentials
Create new API keys immediately
Update Applications
Deploy updated credentials to all services
Audit Logs
Review access logs for suspicious activity
Notify Stakeholders
Inform relevant parties of the incident