Security Best Practices
Essential security practices for production Koywe integrations.
API Credentials
Store Securely
Do:
- Store in environment variables
- Use secret management services (AWS Secrets Manager, HashiCorp Vault)
- Rotate credentials periodically
- Use different credentials for sandbox and production
Don’t:
- Hardcode credentials in source code
- Commit credentials to version control
- Share credentials via email/chat
- Use production credentials in development
// ✅ Good - Environment variables
const API_KEY = process.env.KOYWE_API_KEY;
const SECRET = process.env.KOYWE_SECRET;
// ❌ Bad - Hardcoded
const API_KEY = 'sk_live_abc123...'; // NEVER DO THISEnvironment Separation
| Environment | Purpose | Credentials |
|---|---|---|
| Development | Local development | Sandbox |
| Staging | Pre-production testing | Sandbox |
| Production | Live application | Production |
Rotating API Secrets
Regular secret rotation is a critical security practice. Rotate your API secrets:
- On schedule: Quarterly rotation recommended
- After security events: Suspected compromise, employee offboarding
- For compliance: Many security frameworks require periodic rotation
Rotation Endpoint
Endpoint: POST /api/v1/auth/credentials/rotate-secret
Authentication: Bearer token required
Restriction: Only API users can rotate secrets (not email-authenticated users)
How to Rotate
async function rotateApiSecret(currentToken) {
// Step 1: Call rotate endpoint with current token
const response = await axios.post(
'https://api.koywe.com/api/v1/auth/credentials/rotate-secret',
{},
{
headers: { 'Authorization': `Bearer ${currentToken}` }
}
);
const newSecret = response.data.secret;
console.log('New secret generated (store immediately!)');
// Step 2: Store new secret in your secret manager
await secretManager.update('KOYWE_SECRET', newSecret);
// Step 3: Verify new secret works
const verifyResponse = await axios.post(
'https://api.koywe.com/api/v1/auth/sign-in',
{
apiKey: process.env.KOYWE_API_KEY,
secret: newSecret
}
);
if (verifyResponse.data.token) {
console.log('✓ New secret verified successfully');
// Step 4: Remove old secret from vault
await secretManager.delete('KOYWE_SECRET_OLD');
}
return newSecret;
}def rotate_api_secret(current_token):
# Step 1: Call rotate endpoint with current token
response = requests.post(
'https://api.koywe.com/api/v1/auth/credentials/rotate-secret',
headers={'Authorization': f'Bearer {current_token}'}
)
new_secret = response.json()['secret']
print('New secret generated (store immediately!)')
# Step 2: Store new secret in your secret manager
secret_manager.update('KOYWE_SECRET', new_secret)
# Step 3: Verify new secret works
verify_response = requests.post(
'https://api.koywe.com/api/v1/auth/sign-in',
json={
'apiKey': os.environ['KOYWE_API_KEY'],
'secret': new_secret
}
)
if verify_response.json().get('token'):
print('✓ New secret verified successfully')
# Step 4: Remove old secret from vault
secret_manager.delete('KOYWE_SECRET_OLD')
return new_secret# Rotate secret
curl -X POST 'https://api.koywe.com/api/v1/auth/credentials/rotate-secret' \
-H 'Authorization: Bearer YOUR_CURRENT_TOKEN'
# Response: { "secret": "2af81190b3a153r48a3df3a1eefcc386ca763b99fba53d39666751ffd4e2ae81" }Important: The new secret is only shown once in the response. Store it immediately in your secret manager before discarding the old secret.
Rotation Best Practices
Rotation Checklist:
- Backup current secret before rotating
- Store new secret in secret manager immediately
- Verify new secret works before removing old one
- Update all environments that use the secret
- Log the rotation event for audit purposes
- Test authentication after rotation
Error Handling
| Status Code | Error | Meaning |
|---|---|---|
401 | Unauthorized | Token invalid or expired |
403 | Only API users can rotate | Email-authenticated users cannot rotate secrets |
500 | Internal Server Error | Retry the request |
try {
await rotateApiSecret(token);
} catch (error) {
if (error.response?.status === 403) {
console.error('Secret rotation requires API user authentication, not email login');
} else if (error.response?.status === 401) {
console.error('Token expired - re-authenticate before rotating');
}
}Webhook Security
1. Signature Verification
Always verify webhook signatures:
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return signature === expectedSignature;
}
// In webhook handler
if (!verifyWebhookSignature(req.body, req.headers['koywe-signature'], secret)) {
return res.status(401).send('Invalid signature');
}2. HTTPS Only
- Webhook endpoints must use HTTPS
- Obtain valid SSL certificate
- Redirect HTTP to HTTPS
3. Validate Event Structure
function validateWebhookEvent(event) {
if (!event.id || !event.type || !event.data) {
throw new Error('Invalid event structure');
}
if (!event.data.orderId) {
throw new Error('Missing orderId');
}
return true;
}Token Security
Token Management
class SecureTokenManager {
constructor(apiKey, secret) {
this.apiKey = apiKey;
this.secret = secret;
this.token = null;
this.tokenExpiry = null;
}
async getToken() {
// Check if token is still valid
if (this.token && this.tokenExpiry > Date.now() + (5 * 60 * 1000)) {
return this.token;
}
// Get new token
const response = await axios.post(
'https://api.koywe.com/api/v1/auth/sign-in',
{
apiKey: this.apiKey,
secret: this.secret
}
);
this.token = response.data.token;
this.tokenExpiry = Date.now() + (60 * 60 * 1000); // 1 hour
return this.token;
}
clearToken() {
this.token = null;
this.tokenExpiry = null;
}
}
// Usage
const tokenManager = new SecureTokenManager(API_KEY, SECRET);
const token = await tokenManager.getToken();Token Transmission
- Always use HTTPS for API requests
- Include token in
Authorizationheader (not URL) - Never log tokens
- Clear tokens on logout
// ✅ Good - Header
axios.get(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
// ❌ Bad - URL parameter
axios.get(`${url}?token=${token}`); // NEVER DO THISData Protection
PII (Personally Identifiable Information)
Sensitive Data:
- Customer names
- Email addresses
- Phone numbers
- Document numbers
- Bank account numbers
Best Practices:
const crypto = require('crypto');
function encryptPII(data, key) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
authTag: authTag.toString('hex')
};
}
// Store encrypted data
const encryptedEmail = encryptPII(customer.email, ENCRYPTION_KEY);
await db.save({ encryptedEmail });Logging
Do:
- Log request IDs, timestamps, statuses
- Log errors and exceptions
- Log business events
Don’t Log:
- API credentials
- Tokens
- PII (emails, phone numbers, documents)
- Bank account numbers
- Full credit card numbers
// ✅ Good logging
console.log('Order created:', {
orderId: order.id,
type: order.type,
amount: order.amountIn,
currency: order.originCurrencySymbol
});
// ❌ Bad logging
console.log('Order created:', {
orderId: order.id,
customerEmail: 'customer@example.com', // DON'T LOG PII
token: 'Bearer abc123...' // DON'T LOG TOKENS
});Network Security
Firewall Configuration
Recommended:
- Whitelist Koywe API IPs
- Restrict outbound traffic
- Use VPC/private subnets
- Enable DDoS protection
TLS/SSL
- Use TLS 1.2 or higher
- Verify SSL certificates
- Enable certificate pinning (optional)
const axios = require('axios');
const https = require('https');
// Enforce TLS 1.2+
const agent = new https.Agent({
minVersion: 'TLSv1.2',
rejectUnauthorized: true
});
const api = axios.create({
httpsAgent: agent
});Input Validation
Validate All Inputs
function validateOrderInput(input) {
// Amount
if (typeof input.amount !== 'number' || input.amount <= 0) {
throw new Error('Invalid amount');
}
// Currency
const validCurrencies = ['COP', 'BRL', 'MXN', 'CLP', 'USD'];
if (!validCurrencies.includes(input.currency)) {
throw new Error('Invalid currency');
}
// External ID
if (!/^[a-zA-Z0-9-_]+$/.test(input.externalId)) {
throw new Error('Invalid external ID format');
}
return true;
}Sanitize User Input
function sanitizeDescription(description) {
// Remove potentially harmful characters
return description
.replace(/[<>]/g, '') // Remove < >
.replace(/javascript:/gi, '') // Remove javascript:
.trim()
.substring(0, 255); // Limit length
}Rate Limiting
Implement rate limiting to prevent abuse:
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later.'
});
// Apply to API routes
app.use('/api/', apiLimiter);
// Stricter limit for sensitive operations
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 auth attempts
skipSuccessfulRequests: true
});
app.post('/api/auth', authLimiter, authHandler);Error Handling
Don’t Expose Internal Details
function handleError(error, res) {
// Log detailed error internally
console.error('Internal error:', {
stack: error.stack,
message: error.message,
timestamp: new Date()
});
// Return generic message to client
res.status(500).json({
error: 'An error occurred. Please try again or contact support.',
requestId: generateRequestId()
});
// ❌ Don't do this
// res.status(500).json({ error: error.stack }); // Exposes internals
}Compliance
PCI DSS
If handling card data:
- Never store CVV
- Tokenize card numbers
- Use PCI-compliant infrastructure
- Conduct regular security audits
GDPR/Data Protection
- Obtain user consent for data storage
- Provide data export functionality
- Implement data deletion
- Maintain audit logs
- Encrypt PII
Monitoring and Alerts
Security Monitoring
function monitorSuspiciousActivity(request) {
// Multiple failed auth attempts
if (failedAttempts > 5) {
alertSecurityTeam('Multiple failed auth attempts', {
ip: request.ip,
attempts: failedAttempts
});
}
// Unusual payment amounts
if (amount > THRESHOLD) {
alertSecurityTeam('High-value transaction', {
orderId: order.id,
amount: amount,
currency: currency
});
}
// Geographic anomalies
if (isUnusualLocation(request.ip)) {
alertSecurityTeam('Unusual location', {
ip: request.ip,
location: getLocation(request.ip)
});
}
}Audit Logging
function auditLog(action, details) {
await db.auditLogs.insert({
timestamp: new Date(),
action: action,
userId: details.userId,
resource: details.resource,
changes: details.changes,
ip: details.ip,
userAgent: details.userAgent
});
}
// Usage
await auditLog('order.created', {
userId: user.id,
resource: `order:${order.id}`,
changes: { amount: order.amountIn, currency: order.currency },
ip: req.ip,
userAgent: req.headers['user-agent']
});Security Checklist
Production Checklist:
- API credentials stored in environment variables
- Different credentials for sandbox/production
- HTTPS enforced on all endpoints
- Webhook signature verification implemented
- Token management with expiry handling
- PII encrypted at rest
- Sensitive data not logged
- Input validation on all user inputs
- Rate limiting configured
- Error messages don’t expose internals
- Security monitoring and alerts setup
- Audit logging implemented
- Regular security audits scheduled
- Incident response plan documented
Next Steps
Last updated on