Skip to Content
Advanced TopicsSecurity Best Practices

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 THIS

Environment Separation

EnvironmentPurposeCredentials
DevelopmentLocal developmentSandbox
StagingPre-production testingSandbox
ProductionLive applicationProduction

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 CodeErrorMeaning
401UnauthorizedToken invalid or expired
403Only API users can rotateEmail-authenticated users cannot rotate secrets
500Internal Server ErrorRetry 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 Authorization header (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 THIS

Data 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