Logo
Guides

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:

  1. Immediately revoke compromised keys in dashboard
  2. Generate new keys and update integrations
  3. Review logs for unauthorized access
  4. Notify One2Pays support if suspicious activity detected

Security Breach

If a security breach is detected:

  1. Isolate affected systems
  2. Revoke all API keys
  3. Review all recent transactions
  4. Contact One2Pays support immediately
  5. Notify affected customers if required

On this page