WhatsApp API Security: Best Practices
Panduan keamanan WhatsApp API. Protect credentials, secure webhook, prevent abuse. Security best practices untuk developer!
Security bukan optional!
WhatsApp bot handle data customer. Satu breach bisa hancurkan trust dan bisnis.
Security Layers
┌─────────────────────────────────────┐
│ APPLICATION LAYER │
│ Input validation, rate limit │
├─────────────────────────────────────┤
│ API LAYER │
│ Auth, webhook verification │
├─────────────────────────────────────┤
│ NETWORK LAYER │
│ HTTPS, firewall, VPN │
├─────────────────────────────────────┤
│ INFRASTRUCTURE │
│ Server hardening, updates │
└─────────────────────────────────────┘1. Protect Credentials
Never Hardcode:
javascript
// ❌ NEVER DO THIS
const ACCESS_TOKEN = 'EAABx123...';
const API_KEY = 'sk-abc123...';
// ✅ USE ENVIRONMENT VARIABLES
require('dotenv').config();
const ACCESS_TOKEN = process.env.WHATSAPP_ACCESS_TOKEN;
const API_KEY = process.env.OPENAI_API_KEY;.env File:
env
# .env (NEVER commit to git!)
WHATSAPP_ACCESS_TOKEN=EAABx123...
WHATSAPP_PHONE_ID=1234567890
OPENAI_API_KEY=sk-abc123...
WEBHOOK_VERIFY_TOKEN=my_secret_token.gitignore:
gitignore
.env
.env.local
auth_info/
*.pem
*.key
node_modules/2. Secure Webhook
Verify Signature:
javascript
const crypto = require('crypto');
function verifyWebhookSignature(req, appSecret) {
const signature = req.headers['x-hub-signature-256'];
if (!signature) {
return false;
}
const payload = JSON.stringify(req.body);
const expectedSignature = 'sha256=' +
crypto.createHmac('sha256', appSecret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Middleware
app.post('/webhook', (req, res) => {
if (!verifyWebhookSignature(req, process.env.APP_SECRET)) {
console.log('Invalid webhook signature');
return res.sendStatus(401);
}
// Process verified webhook
processWebhook(req.body);
res.sendStatus(200);
});Verify Token:
javascript
app.get('/webhook', (req, res) => {
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];
// Use constant-time comparison
const expectedToken = process.env.WEBHOOK_VERIFY_TOKEN;
const tokenValid = crypto.timingSafeEqual(
Buffer.from(token || ''),
Buffer.from(expectedToken)
);
if (mode === 'subscribe' && tokenValid) {
res.status(200).send(challenge);
} else {
res.sendStatus(403);
}
});3. Input Validation
Sanitize User Input:
javascript
const validator = require('validator');
function sanitizeMessage(text) {
if (!text || typeof text !== 'string') {
return '';
}
// Remove potential injection
let sanitized = text
.replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/javascript:/gi, '') // Remove JS protocol
.trim();
// Limit length
if (sanitized.length > 4096) {
sanitized = sanitized.substring(0, 4096);
}
return sanitized;
}
// Validate phone number
function validatePhone(phone) {
// Indonesian format
const phoneRegex = /^62[0-9]{9,13}$/;
return phoneRegex.test(phone);
}Prevent Command Injection:
javascript
// ❌ DANGEROUS
const userInput = message.text;
exec(`echo ${userInput}`); // Shell injection risk!
// ✅ SAFE
const userInput = sanitizeMessage(message.text);
// Never pass user input to shell commands4. Rate Limiting
javascript
const rateLimit = require('express-rate-limit');
// Global rate limit
const globalLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
message: 'Too many requests'
});
// Webhook specific limit
const webhookLimiter = rateLimit({
windowMs: 1000, // 1 second
max: 50 // 50 per second (high for webhooks)
});
app.use('/api', globalLimiter);
app.use('/webhook', webhookLimiter);Per-User Rate Limit:
javascript
const userLimits = new Map();
function checkUserLimit(userId) {
const now = Date.now();
const userHistory = userLimits.get(userId) || [];
// Remove old entries (older than 1 minute)
const recent = userHistory.filter(t => now - t < 60000);
if (recent.length >= 20) { // Max 20 messages/minute
return false;
}
recent.push(now);
userLimits.set(userId, recent);
return true;
}5. Data Protection
Encrypt Sensitive Data:
javascript
const crypto = require('crypto');
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // 32 bytes
const IV_LENGTH = 16;
function encrypt(text) {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv('aes-256-cbc', ENCRYPTION_KEY, iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
function decrypt(text) {
const parts = text.split(':');
const iv = Buffer.from(parts.shift(), 'hex');
const encrypted = Buffer.from(parts.join(':'), 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', ENCRYPTION_KEY, iv);
let decrypted = decipher.update(encrypted);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}Mask Sensitive Info in Logs:
javascript
function maskPhone(phone) {
if (!phone) return '';
return phone.slice(0, 4) + '****' + phone.slice(-4);
}
function maskData(data) {
return {
...data,
phone: maskPhone(data.phone),
email: data.email?.replace(/(.{2})(.*)(@.*)/, '$1***$3')
};
}
// Logging
console.log('Processing message from:', maskPhone(message.from));6. Auth Session Security
javascript
// Store auth securely
const authPath = process.env.AUTH_PATH || './auth_info';
// Set proper permissions
const fs = require('fs');
fs.chmodSync(authPath, 0o700); // Owner only
// Encrypt auth files at rest (optional but recommended)
async function saveAuthEncrypted(state) {
const encrypted = encrypt(JSON.stringify(state));
fs.writeFileSync(`${authPath}/creds.enc`, encrypted);
}7. Server Hardening
HTTPS Only:
javascript
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('private.key'),
cert: fs.readFileSync('certificate.crt')
};
https.createServer(options, app).listen(443);
// Redirect HTTP to HTTPS
app.use((req, res, next) => {
if (!req.secure) {
return res.redirect('https://' + req.headers.host + req.url);
}
next();
});Security Headers:
javascript
const helmet = require('helmet');
app.use(helmet());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}));Security Checklist
□ Credentials in environment variables
□ .env in .gitignore
□ Webhook signature verification
□ Input validation & sanitization
□ Rate limiting implemented
□ HTTPS only
□ Security headers (helmet)
□ Auth files protected
□ Sensitive data encrypted
□ Logs sanitized (no PII in plain text)
□ Regular dependency updates
□ Error messages don't leak infoFAQ
Bagaimana jika credentials bocor?
Immediate action: Rotate semua credentials, review access logs, notify affected users.
Perlu penetration testing?
Recommended untuk production. Minimal gunakan tools seperti OWASP ZAP untuk basic scan.
Bagaimana comply dengan data protection laws?
Implement: consent collection, data minimization, right to deletion, encryption at rest.
Kesimpulan
Security = Foundation, bukan afterthought!
| Layer | Protection |
|---|---|
| Credentials | Env vars, never hardcode |
| Webhook | Signature verification |
| Input | Validation & sanitization |
| Data | Encryption & masking |
| Server | HTTPS, headers, hardening |
Secure by design!