Security
Best practices for securing your integration with FromChain payment gateway.
API Key Management
Never Expose API Keys
Keep API keys server-side only. Never expose them in client-side code, mobile apps, or public repositories.
Use Environment Variables
Store keys in environment variables or secure secret management systems like AWS Secrets Manager or HashiCorp Vault.
Rotate Keys Regularly
Rotate API keys every 90 days or immediately if you suspect a compromise.
Use Scoped Keys
Create API keys with minimal required permissions. Use read-only keys for monitoring and full-access keys only when necessary.
.env files to your .gitignore.# API Configuration
FROMCHAIN_API_KEY=gw_live_your_key_here
FROMCHAIN_WEBHOOK_SECRET=whsec_your_secret_here
# Never commit .env to git!
# Add to .gitignore:
# .env
# .env.localWebhook Signature Verification
Always verify webhook signatures to prevent spoofing and replay attacks.
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string,
timestamp: string
): boolean {
// Prevent replay attacks (reject requests older than 5 minutes)
const currentTime = Date.now();
const webhookTime = parseInt(timestamp);
const fiveMinutes = 5 * 60 * 1000;
if (currentTime - webhookTime > fiveMinutes) {
throw new Error('Webhook timestamp too old');
}
// Verify HMAC signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
const expected = Buffer.from(`v1=${expectedSignature}`);
const actual = Buffer.from(signature);
// Use timing-safe comparison to prevent timing attacks
if (expected.length !== actual.length) {
return false;
}
return timingSafeEqual(expected, actual);
}
// Express.js example
app.post('/api/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const payload = req.body.toString('utf8');
try {
const isValid = verifyWebhookSignature(
payload,
signature,
process.env.FROMCHAIN_WEBHOOK_SECRET,
timestamp
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
const event = JSON.parse(payload);
console.log('Verified webhook:', event.type);
res.json({ received: true });
} catch (error) {
res.status(400).json({ error: error.message });
}
});Security Checklist:
Wallet Address Best Practices
Validate Addresses
Always validate BSC wallet addresses before using them. Ensure they are valid BEP-20 addresses.
Test First
When updating payout addresses, send a small test transaction first to verify the address is correct.
Use Cold Storage
For large amounts, consider using a hot wallet for payouts and regularly transferring to cold storage.
import { ethers } from 'ethers';
function isValidBSCAddress(address: string): boolean {
try {
// Check if it's a valid Ethereum-style address
return ethers.utils.isAddress(address);
} catch (error) {
return false;
}
}
// Example usage
const payoutAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
if (!isValidBSCAddress(payoutAddress)) {
throw new Error('Invalid BSC wallet address');
}
console.log('✓ Valid address'):HTTPS Requirements
All webhook endpoints must use HTTPS
Webhook URLs must use TLS 1.2 or higher. HTTP endpoints will be rejected.
https://yourdomain.com/webhookshttp://yourdomain.com/webhooksIdempotency
Use idempotency keys to safely retry requests without creating duplicates.
import { v4 as uuidv4 } from 'uuid';
async function createInvoice(orderData) {
// Generate a unique idempotency key based on your order ID
const idempotencyKey = `order_${orderData.id}_${Date.now()}`;
const response = await fetch('https://api.fromchain.io/v1/invoices', {
method: 'POST',
headers: {
'Authorization': 'Bearer gw_live_...',
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey // Prevent duplicates
},
body: JSON.stringify({
amountExpected: orderData.amount,
externalId: orderData.id,
description: orderData.description
})
});
if (!response.ok) {
// Safe to retry with same idempotency key
throw new Error('Failed to create invoice');
}
return response.json();
}
// If the request fails due to network issues, retrying with
// the same idempotency key will return the original invoice
// instead of creating a duplicate.💡 Tip: Idempotency keys are valid for 24 hours. The same key will return the same response within this window.
Rate Limiting Compliance
Respect rate limits to ensure reliable service for all users.
Current Limits
- • 100 requests per minute
- • 1000 requests per hour
- • Per API key basis
Response Headers
- X-RateLimit-Limit
- X-RateLimit-Remaining
- X-RateLimit-Reset
async function makeAPIRequest(url, options) {
const response = await fetch(url, options);
// Check rate limit headers
const limit = response.headers.get('X-RateLimit-Limit');
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');
if (response.status === 429) {
// Rate limit exceeded
const retryAfter = parseInt(reset) - Date.now();
console.warn(`Rate limited. Retry after ${retryAfter}ms`);
// Implement exponential backoff
await new Promise(resolve => setTimeout(resolve, retryAfter));
return makeAPIRequest(url, options);
}
// Log when approaching limit
if (parseInt(remaining) < 10) {
console.warn(`Only ${remaining} requests remaining`);
}
return response;
}Secure Error Handling
Don't Expose Sensitive Data
Never return API keys, webhook secrets, or internal error details to clients.
Log Securely
Sanitize logs to exclude sensitive information. Never log full API keys or secrets.
// ❌ Bad: Logs full API key
console.log('API Key:', process.env.FROMCHAIN_API_KEY);
// ✅ Good: Logs only last 4 characters
const apiKey = process.env.FROMCHAIN_API_KEY;
const masked = 'gw_****' + apiKey.slice(-4);
console.log('API Key:', masked);
// ✅ Good: Sanitize error responses
app.use((error, req, res, next) => {
// Log full error server-side
console.error('Internal error:', error);
// Return sanitized error to client
res.status(500).json({
error: 'Internal server error',
requestId: req.id,
// Don't expose: stack traces, database errors, keys, etc.
});
});Security Monitoring
Monitor for Anomalies
- • Unusual API request patterns
- • Failed authentication attempts
- • Unexpected webhook deliveries
- • Large or unusual payout requests
Set Up Alerts
- • Failed payout notifications
- • Rate limit approaching threshold
- • Invalid webhook signatures
- • Unexpected invoice statuses
Compliance & Data Protection
Data Retention
FromChain retains transaction data for 7 years for compliance purposes. Invoice metadata is stored as provided.
PII Handling
Avoid storing personally identifiable information (PII) in invoice metadata or descriptions unless necessary for your business.
KYC/AML Compliance
Implement your own KYC/AML procedures. FromChain provides the payment infrastructure, but compliance with local regulations is your responsibility.