WhatsApp API Session Management

Panduan session management WhatsApp API. Authentication, reconnect, multi-device, persistent session. Tutorial developer!

WhatsApp API Session Management
WhatsApp API Session Management

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 complex

Official 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 credentials

DON'T ❌

- Simpan session di memory saja
- Ignore disconnect events
- Reconnect tanpa delay
- No monitoring
- Single session untuk banyak user
- Store credentials plain text

FAQ

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 SessionGood Session
Sering disconnectStable connection
Manual reconnectAuto reconnect
Lost after restartPersistent
No monitoringFull visibility

Setup Session Management →


Artikel Terkait