Security Best Practices
Security is critical when handling payments. Follow these best practices to ensure your integration is secure.
API Key Security
Store Credentials Securely
Never commit API keys to version control
Always use environment variables or secure secret management.
✅ Good:
// Use environment variables
const API_KEY = process.env.PAYMENT_API_KEY!;
const API_SECRET = process.env.PAYMENT_API_SECRET!;❌ Bad:
// Never hardcode credentials
const API_KEY = 'sk_live_1234567890abcdef';
const API_SECRET = 'secret_1234567890abcdef';Rotate Keys Regularly
- Rotate API keys every 90 days
- Use different keys for different environments (development, staging, production)
- Revoke compromised keys immediately
Restrict Key Permissions
- Use integration-specific keys when possible
- Limit key permissions to only what's needed
- Monitor key usage in the dashboard
HMAC Signature Security
Always Verify Signatures
Always verify HMAC signatures for webhook requests:
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature.replace('sha256=', '')),
Buffer.from(expectedSignature)
);
}Use Timing-Safe Comparison
Always use timing-safe comparison functions to prevent timing attacks:
- Node.js: Use
crypto.timingSafeEqual() - Python: Use
hmac.compare_digest()
Request Security
Use HTTPS Only
Always use HTTPS
Never send API requests over HTTP in production.
- Use HTTPS for all API requests
- Use HTTPS for webhook endpoints
- Verify SSL certificates
Validate Timestamps
Always validate request timestamps to prevent replay attacks:
function validateTimestamp(timestamp: string): boolean {
const requestTime = parseInt(timestamp); // Timestamp is in milliseconds
const currentTime = Date.now(); // Current time in milliseconds
const timeDifference = Math.abs(currentTime - requestTime);
// Reject requests older than 5 minutes (300000 milliseconds)
return timeDifference <= 300000;
}Use Idempotency Keys
Always use idempotency keys for payment creation to prevent duplicate charges:
const payment = await createPayment({
amount: '1000.00',
currency: 'THB',
referenceId: 'order-12345',
paymentMethod: 'promptpay',
idempotencyKey: `order-12345-${Date.now()}`, // Unique key
});Webhook Security
Verify Webhook Signatures
Always verify webhook signatures before processing events:
export async function POST(request: Request) {
const payload = await request.text();
const signature = request.headers.get('x-webhook-signature');
if (!signature || !verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return new Response('Invalid signature', { status: 401 });
}
// Process webhook...
}Use HTTPS for Webhooks
- Webhook endpoints must be publicly accessible
- Use HTTPS for all webhook endpoints
- Verify SSL certificates
Implement Rate Limiting
Implement rate limiting on webhook endpoints to prevent abuse:
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '1 m'),
});
export async function POST(request: Request) {
const ip = request.headers.get('x-forwarded-for') || 'unknown';
const { success } = await ratelimit.limit(ip);
if (!success) {
return new Response('Rate limit exceeded', { status: 429 });
}
// Process webhook...
}Data Security
Never Log Sensitive Data
Never log API secrets, payment details, or customer information
This includes console.log, file logs, and error messages.
✅ Good:
console.log('Payment created:', payment.id);
console.log('Payment status:', payment.status);❌ Bad:
console.log('API Secret:', API_SECRET);
console.log('Payment details:', JSON.stringify(payment));
console.log('Customer bank account:', payment.customerBankAccount);Sanitize Error Messages
Never expose sensitive information in error messages:
✅ Good:
if (!result.success) {
return { error: 'Payment creation failed' };
}❌ Bad:
if (!result.success) {
return { error: result.error.message }; // May contain sensitive data
}Encrypt Sensitive Data
- Encrypt sensitive data at rest
- Use encrypted database connections
- Use secure key management services
Network Security
Use IP Whitelisting
Consider IP whitelisting for webhook endpoints:
const ALLOWED_IPS = ['203.0.113.0', '198.51.100.0']; // <AppName /> IPs
export async function POST(request: Request) {
const ip = request.headers.get('x-forwarded-for');
if (!ALLOWED_IPS.includes(ip || '')) {
return new Response('Forbidden', { status: 403 });
}
// Process webhook...
}Use Firewall Rules
- Configure firewall rules to restrict access
- Use WAF (Web Application Firewall) for additional protection
- Monitor network traffic for suspicious activity
Compliance
PCI DSS Compliance
If handling card data:
- Never store card numbers
- Use tokenization when possible
- Follow PCI DSS guidelines
Data Protection
- Follow GDPR guidelines for EU customers
- Implement data retention policies
- Provide data deletion capabilities
Monitoring & Logging
Monitor API Usage
- Monitor API usage for unusual patterns
- Set up alerts for failed authentication attempts
- Track webhook delivery failures
Audit Logging
- Log all payment operations
- Log authentication attempts
- Log webhook processing
Incident Response
Key Compromise
If API keys are compromised:
- Immediately revoke compromised keys in dashboard
- Generate new keys and update integrations
- Review logs for unauthorized access
- Notify One2Pays support if suspicious activity detected
Security Breach
If a security breach is detected:
- Isolate affected systems
- Revoke all API keys
- Review all recent transactions
- Contact One2Pays support immediately
- Notify affected customers if required