WhatsApp API Session Management
Panduan session management WhatsApp API. Authentication, reconnect, multi-device, persistent session. Tutorial developer!
Session = Nyawa bot kamu!
Session management yang baik memastikan bot tetap terhubung, auto-reconnect saat disconnect, dan persist across restarts.
Official vs Unofficial Session
📊 PERBEDAAN:
OFFICIAL API (Cloud API):
- Token-based authentication
- No session file
- Always connected (Meta's server)
- Simple & reliable
UNOFFICIAL (Baileys, etc):
- QR code authentication
- Session file/database
- Perlu reconnect handling
- More complexOfficial API Authentication
Access Token:
javascript
// Official API menggunakan access token
const ACCESS_TOKEN = process.env.WHATSAPP_ACCESS_TOKEN;
// Token types:
// 1. User Access Token - temporary, perlu refresh
// 2. System User Token - permanent, recommended untuk production
async function sendMessage(to, message) {
const response = await axios.post(
`https://graph.facebook.com/v17.0/${PHONE_NUMBER_ID}/messages`,
{
messaging_product: 'whatsapp',
to: to,
type: 'text',
text: { body: message }
},
{
headers: {
'Authorization': `Bearer ${ACCESS_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
}Token Refresh:
javascript
// Untuk User Access Token yang expire
async function refreshToken(shortLivedToken) {
const response = await axios.get(
'https://graph.facebook.com/v17.0/oauth/access_token',
{
params: {
grant_type: 'fb_exchange_token',
client_id: APP_ID,
client_secret: APP_SECRET,
fb_exchange_token: shortLivedToken
}
}
);
return response.data.access_token;
}Unofficial API Session (Baileys)
Basic Session Setup:
javascript
const {
default: makeWASocket,
useMultiFileAuthState,
DisconnectReason
} = require('@whiskeysockets/baileys');
async function startBot() {
// Load session dari folder
const { state, saveCreds } = await useMultiFileAuthState('./session');
const sock = makeWASocket({
auth: state,
printQRInTerminal: true
});
// Save credentials saat update
sock.ev.on('creds.update', saveCreds);
// Handle connection updates
sock.ev.on('connection.update', (update) => {
const { connection, lastDisconnect, qr } = update;
if (qr) {
console.log('Scan QR code ini:');
// QR akan di-print di terminal
}
if (connection === 'open') {
console.log('Connected!');
}
if (connection === 'close') {
handleDisconnect(lastDisconnect);
}
});
return sock;
}
startBot();Handle Disconnect & Reconnect:
javascript
function handleDisconnect(lastDisconnect) {
const statusCode = lastDisconnect?.error?.output?.statusCode;
const reason = DisconnectReason[statusCode] || 'Unknown';
console.log(`Disconnected: ${reason} (${statusCode})`);
// Determine if should reconnect
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
if (shouldReconnect) {
console.log('Reconnecting...');
startBot(); // Restart bot
} else {
console.log('Logged out. Need to scan QR again.');
// Delete session dan minta scan ulang
fs.rmSync('./session', { recursive: true, force: true });
startBot();
}
}
// Disconnect reasons:
// 401 - Logged out (perlu scan QR ulang)
// 408 - Connection lost (auto reconnect)
// 411 - Multi-device mismatch
// 428 - Connection replaced (login di tempat lain)
// 440 - Connection closed by server
// 500 - Bad session (perlu scan ulang)Persistent Session (Database)
Simpan Session ke MongoDB:
javascript
const { MongoClient } = require('mongodb');
// Custom auth state dengan MongoDB
async function useMongoDBAuthState(sessionId) {
const client = await MongoClient.connect(MONGO_URI);
const db = client.db('whatsapp');
const collection = db.collection('sessions');
// Read
const readData = async (key) => {
const doc = await collection.findOne({
sessionId,
key
});
return doc?.value;
};
// Write
const writeData = async (key, value) => {
await collection.updateOne(
{ sessionId, key },
{ $set: { value, updatedAt: new Date() } },
{ upsert: true }
);
};
// Delete
const removeData = async (key) => {
await collection.deleteOne({ sessionId, key });
};
// Load existing state
const creds = await readData('creds');
const keys = await readData('keys') || {};
return {
state: {
creds: creds || initAuthCreds(),
keys: makeCacheableSignalKeyStore(keys)
},
saveCreds: async () => {
await writeData('creds', sock.authState.creds);
await writeData('keys', sock.authState.keys);
}
};
}
// Usage
const { state, saveCreds } = await useMongoDBAuthState('bot-session-1');
const sock = makeWASocket({
auth: state,
printQRInTerminal: true
});
sock.ev.on('creds.update', saveCreds);Simpan Session ke Redis:
javascript
const Redis = require('ioredis');
const redis = new Redis();
async function useRedisAuthState(sessionId) {
const prefix = `wa:session:${sessionId}:`;
const readData = async (key) => {
const data = await redis.get(prefix + key);
return data ? JSON.parse(data) : null;
};
const writeData = async (key, value) => {
await redis.set(prefix + key, JSON.stringify(value));
};
const removeData = async (key) => {
await redis.del(prefix + key);
};
const creds = await readData('creds');
return {
state: {
creds: creds || initAuthCreds(),
keys: {
get: async (type, ids) => {
const data = {};
for (const id of ids) {
const value = await readData(`${type}-${id}`);
if (value) data[id] = value;
}
return data;
},
set: async (data) => {
for (const category in data) {
for (const id in data[category]) {
await writeData(`${category}-${id}`, data[category][id]);
}
}
}
}
},
saveCreds: async () => {
await writeData('creds', sock.authState.creds);
}
};
}Multi-Session Management
Multiple Bots/Numbers:
javascript
class WhatsAppManager {
constructor() {
this.sessions = new Map();
}
async createSession(sessionId) {
if (this.sessions.has(sessionId)) {
return this.sessions.get(sessionId);
}
const { state, saveCreds } = await useMultiFileAuthState(
`./sessions/${sessionId}`
);
const sock = makeWASocket({
auth: state,
printQRInTerminal: true
});
sock.ev.on('creds.update', saveCreds);
sock.ev.on('connection.update', (update) => {
if (update.connection === 'close') {
this.handleDisconnect(sessionId, update.lastDisconnect);
}
});
this.sessions.set(sessionId, sock);
return sock;
}
getSession(sessionId) {
return this.sessions.get(sessionId);
}
async deleteSession(sessionId) {
const sock = this.sessions.get(sessionId);
if (sock) {
await sock.logout();
this.sessions.delete(sessionId);
}
// Hapus file session
fs.rmSync(`./sessions/${sessionId}`, { recursive: true, force: true });
}
handleDisconnect(sessionId, lastDisconnect) {
const statusCode = lastDisconnect?.error?.output?.statusCode;
if (statusCode !== DisconnectReason.loggedOut) {
// Auto reconnect
setTimeout(() => this.createSession(sessionId), 5000);
} else {
// Logged out, perlu scan ulang
this.sessions.delete(sessionId);
}
}
// Send message from specific session
async sendMessage(sessionId, to, message) {
const sock = this.sessions.get(sessionId);
if (!sock) {
throw new Error(`Session ${sessionId} not found`);
}
return await sock.sendMessage(`${to}@s.whatsapp.net`, { text: message });
}
}
// Usage
const manager = new WhatsAppManager();
// Create multiple sessions
await manager.createSession('business-1');
await manager.createSession('business-2');
await manager.createSession('support');
// Send from specific session
await manager.sendMessage('business-1', '628123456789', 'Hai dari Business 1!');Health Check & Monitoring
javascript
class SessionMonitor {
constructor(sock, sessionId) {
this.sock = sock;
this.sessionId = sessionId;
this.lastPing = null;
this.isConnected = false;
}
start() {
// Listen to connection updates
this.sock.ev.on('connection.update', (update) => {
if (update.connection === 'open') {
this.isConnected = true;
this.lastPing = new Date();
}
if (update.connection === 'close') {
this.isConnected = false;
}
});
// Periodic health check
setInterval(() => this.healthCheck(), 30000);
}
async healthCheck() {
const status = {
sessionId: this.sessionId,
isConnected: this.isConnected,
lastPing: this.lastPing,
uptime: this.lastPing ? Date.now() - this.lastPing.getTime() : null
};
// Log atau kirim ke monitoring service
console.log('Health check:', status);
// Alert jika disconnected > 5 menit
if (!this.isConnected) {
await this.sendAlert('Session disconnected!');
}
return status;
}
async sendAlert(message) {
// Kirim ke Telegram, Slack, email, etc
console.error(`ALERT [${this.sessionId}]: ${message}`);
}
}QR Code Handling
Generate QR untuk Web:
javascript
const QRCode = require('qrcode');
const express = require('express');
const app = express();
let currentQR = null;
sock.ev.on('connection.update', async (update) => {
if (update.qr) {
// Convert QR string to image
currentQR = await QRCode.toDataURL(update.qr);
}
if (update.connection === 'open') {
currentQR = null;
}
});
// Endpoint untuk get QR
app.get('/qr', (req, res) => {
if (currentQR) {
res.json({ qr: currentQR });
} else {
res.json({ status: 'connected' });
}
});
// HTML page untuk scan
app.get('/scan', (req, res) => {
res.send(`
<html>
<body>
<div id="qr"></div>
<script>
async function checkQR() {
const res = await fetch('/qr');
const data = await res.json();
if (data.qr) {
document.getElementById('qr').innerHTML =
'<img src="' + data.qr + '">';
} else {
document.getElementById('qr').innerHTML =
'<h2>Connected!</h2>';
}
}
setInterval(checkQR, 2000);
checkQR();
</script>
</body>
</html>
`);
});Best Practices
DO ✅
- Persist session ke database
- Handle all disconnect reasons
- Auto-reconnect dengan backoff
- Monitor session health
- Multiple session untuk scale
- Encrypt stored credentialsDON'T ❌
- Simpan session di memory saja
- Ignore disconnect events
- Reconnect tanpa delay
- No monitoring
- Single session untuk banyak user
- Store credentials plain textFAQ
Session hilang setelah restart?
Karena disimpan di memory. Gunakan persistent storage (file/database).
Kenapa sering disconnect?
Bisa karena: internet tidak stabil, login di device lain, atau ban.
Berapa lama session valid?
Unofficial: sampai logout atau banned. Official: token bisa expire, gunakan System User Token.
Kesimpulan
Session management = Uptime!
| Poor Session | Good Session |
|---|---|
| Sering disconnect | Stable connection |
| Manual reconnect | Auto reconnect |
| Lost after restart | Persistent |
| No monitoring | Full visibility |