Security Best Practices
Secure your ShadhinPay integration
Security Best Practices
Follow these guidelines to keep your payment integration secure.
API Key Security
Never Expose Secret Keys
Your Secret Key should never appear in:
- Client-side JavaScript
- Mobile app source code
- Git repositories
- Browser network requests
- Log files
Use Environment Variables
// Server-side only
const secretKey = process.env.SHADHINPAY_SECRET_KEY;// Never do this!
const secretKey = 'sk_live_abc123...';Separate Keys by Environment
SHADHINPAY_CLIENT_ID=cl_test_xxx
SHADHINPAY_SECRET_KEY=sk_test_xxxSHADHINPAY_CLIENT_ID=cl_live_xxx
SHADHINPAY_SECRET_KEY=sk_live_xxxWebhook Security
Always Verify Signatures
Never process webhooks without verifying the signature:
function verifyWebhook(payload, signature, secretKey) {
const crypto = require('crypto');
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(JSON.stringify(payload))
.digest('hex');
const providedSignature = signature.replace('sha256=', '');
// Use timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(providedSignature)
);
}Validate Webhook Data
Don't trust webhook data blindly:
app.post('/webhook', async (req, res) => {
const { payment_id, amount } = req.body.data;
// Verify with our API
const payment = await shadhinpay.payments.get(payment_id);
if (payment.amount !== amount) {
console.error('Amount mismatch!');
return res.status(400).send('Invalid data');
}
// Process...
});Server Security
Use HTTPS Only
All communication with ShadhinPay must use HTTPS:
// Correct
const baseUrl = 'https://api.shadhinpay.com';
// Wrong - Never use HTTP
const baseUrl = 'http://api.shadhinpay.com';Implement Rate Limiting
Protect your webhook endpoint:
const rateLimit = require('express-rate-limit');
const webhookLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
});
app.post('/webhook', webhookLimiter, handleWebhook);Validate IP Addresses (Optional)
Whitelist ShadhinPay's IP addresses:
const ALLOWED_IPS = [
'103.xxx.xxx.xxx',
'103.xxx.xxx.xxx',
// Get current IPs from dashboard
];
function validateIP(req, res, next) {
const ip = req.ip || req.connection.remoteAddress;
if (!ALLOWED_IPS.includes(ip)) {
return res.status(403).send('Forbidden');
}
next();
}Data Protection
Minimize Data Storage
Only store what you need:
// Good - Store reference only
const order = {
payment_id: 'SP_123',
status: 'PAID',
amount: 1000,
};
// Bad - Don't store sensitive payment details
const order = {
payment_id: 'SP_123',
customer_pin: '1234', // Never store this!
card_number: '4111...', // Never store this!
};Encrypt Sensitive Data
If you must store sensitive data:
const crypto = require('crypto');
function encrypt(text, key) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
const tag = cipher.getAuthTag();
return {
iv: iv.toString('hex'),
content: encrypted.toString('hex'),
tag: tag.toString('hex'),
};
}Input Validation
Validate All Inputs
const { z } = require('zod');
const paymentSchema = z.object({
amount: z.number().min(10).max(500000),
currency: z.enum(['BDT']),
customer_phone: z.string().regex(/^880\d{10}$/),
customer_email: z.string().email().optional(),
});
function createPayment(data) {
const validated = paymentSchema.parse(data);
// Proceed with validated data
}Sanitize User Input
const sanitizeHtml = require('sanitize-html');
function sanitizeInput(input) {
return sanitizeHtml(input, {
allowedTags: [],
allowedAttributes: {},
});
}Logging & Monitoring
Log Security Events
function logSecurityEvent(type, details) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
type,
details,
// Don't log sensitive data
}));
}
// Usage
logSecurityEvent('WEBHOOK_SIGNATURE_INVALID', {
ip: req.ip,
payment_id: req.body.payment_id,
});Monitor for Anomalies
Set up alerts for:
- Multiple failed signature verifications
- Unusual payment amounts
- Requests from unexpected IPs
- High error rates
Security Checklist
- Secret keys stored in environment variables
- Webhook signatures verified
- HTTPS used for all requests
- Rate limiting implemented
- Input validation in place
- Sensitive data encrypted at rest
- Security logging enabled
- Keys rotated periodically
- Access restricted by IP (optional)
- PCI DSS compliance reviewed