Koywe
On this page

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
JavaScript
// ✅ 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;
}

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
JavaScript
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:

JavaScript
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

JavaScript
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

JavaScript
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
JavaScript
// ✅ 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:

JavaScript
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
JavaScript
// ✅ 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)
JavaScript
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

JavaScript
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

JavaScript
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:

JavaScript
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

JavaScript
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

JavaScript
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

JavaScript
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