Skip to main content

Overview

WAPI provides a flexible authentication system that allows you to persist WhatsApp sessions across different storage backends. All authentication strategies implement the IBotAuth interface, ensuring consistent behavior regardless of your chosen storage method.

LocalAuth

File-based storage for simple deployments

RedisAuth

Redis storage for distributed systems

MongoAuth

MongoDB storage for document-based persistence

IBotAuth Interface

All authentication strategies implement this interface from src/types/bot.ts:
export interface IBotAuth {
  init: () => Promise<IBotAuthInit>;
  save: () => Promise<void>;
  remove: () => Promise<void>;
}

export interface IBotAuthInit {
  creds: AuthenticationCreds;
  keys: SignalKeyStore;
}

Methods

  • init(): Initializes and loads the authentication state (credentials and encryption keys)
  • save(): Persists the current authentication state
  • remove(): Deletes the authentication state (logout)
All auth data is encrypted using AES-256-GCM with the bot’s UUID as the encryption key. This ensures session security even if storage is compromised.

Authentication Strategies

LocalAuth

File-based authentication that stores session data in encrypted files on the local filesystem.When to use:
  • Single-server deployments
  • Development and testing
  • Simple bot setups without infrastructure dependencies

Usage

import { Bot, LocalAuth } from 'wapi';
import { randomUUID } from 'crypto';

const uuid = randomUUID();
const auth = new LocalAuth(uuid, './sessions');
const bot = new Bot(uuid, auth, {
  jid: '',
  pn: '+1234567890',
  name: 'My Bot'
});

await bot.login('qr');

Constructor

constructor(uuid: UUID, directory: string)
Parameters:
  • uuid: Unique identifier for the bot (used as encryption key)
  • directory: Path where session files will be stored (relative or absolute)

How It Works

From src/core/auth/local.ts:
export class LocalAuth {
  private directory: string;
  private aes: AES256GCM;
  private cache = new Map<string, Buffer>();
  private creds?: AuthenticationCreds;
  public uuid: UUID;

  constructor(uuid: UUID, directory: string) {
    if (!isUUID(uuid)) {
      throw new Error(`'${uuid}' is not a valid UUID.`);
    }
    // Creates a subdirectory named after the UUID
    this.directory = path.isAbsolute(directory) 
      ? path.join(directory, uuid) 
      : path.resolve(directory, uuid);
    this.aes = new AES256GCM(uuid);
    this.uuid = uuid;
  }
}

Storage Structure

sessions/
└── 550e8400-e29b-41d4-a716-446655440000/
    ├── 5d41402abc4b2a76b9719d911017c592.enc  # creds
    ├── 7a614fd4c3b8f6e91d02c8b1f3b3e6e2.enc  # pre-key-1
    └── ...
Each file is:
  • Named using MD5 hash of the key
  • Encrypted with AES-256-GCM
  • Cached in memory after first read

Example: Complete Setup

import { Bot, LocalAuth } from 'wapi';
import { randomUUID } from 'crypto';
import path from 'path';

// Store sessions in a specific directory
const sessionsDir = path.join(__dirname, 'data', 'sessions');
const uuid = randomUUID();
const auth = new LocalAuth(uuid, sessionsDir);

const bot = new Bot(uuid, auth, {
  jid: '',
  pn: '',
  name: 'LocalAuth Bot'
});

bot.on('open', (account) => {
  console.log('Session saved to:', auth['directory']);
});

await bot.login('qr');
LocalAuth creates the directory automatically if it doesn’t exist. The session files persist across bot restarts, so you only need to scan the QR code once.

Encryption Details

All authentication strategies use AES-256-GCM encryption with the bot’s UUID as the key:
import { AES256GCM } from 'wapi';

const aes = new AES256GCM(uuid);
const encrypted = aes.encrypt(Buffer.from('sensitive data'));
const decrypted = aes.decrypt(encrypted);

Data Serialization

Auth data is serialized with special handling for Buffer objects (from src/core/auth/local.ts lines 40-64):
// Encryption: Buffer → Base64 → Encrypt
const stringified = JSON.stringify(value, (_, value) => {
  if (!isBuffer(value) && !isUint8Array(value) && value?.type !== "Buffer") {
    return value;
  }
  return {
    type: "Buffer",
    data: Buffer.from(value.data ?? value).toString("base64"),
  };
});
const encrypted = this.aes.encrypt(Buffer.from(stringified, "utf8"));

// Decryption: Decrypt → Parse → Restore Buffers
const decrypted = this.aes.decrypt(encrypted);
const parsed = JSON.parse(decrypted.toString("utf8"), (_, value) => {
  if (value?.type !== "Buffer" && !isString(value?.data)) {
    return value;
  }
  return Buffer.from(value.data, "base64");
});

Session Lifecycle

1. First Login (No Session)

const auth = new LocalAuth(uuid, './sessions');

// init() creates new credentials
const { creds, keys } = await auth.init();
// creds = initAuthCreds() - new session

const bot = new Bot(uuid, auth, { jid: '', pn: '', name: '' });
await bot.login('qr'); // Scan QR code

// On successful connection, credentials are saved
bot.on('open', async () => {
  await auth.save(); // Persists session
});

2. Subsequent Logins (Existing Session)

const auth = new LocalAuth(uuid, './sessions');

// init() loads existing credentials
const { creds, keys } = await auth.init();
// creds = loaded from storage - existing session

const bot = new Bot(uuid, auth, { jid: '', pn: '', name: '' });
await bot.login('qr'); // Connects automatically, no QR needed

3. Logout and Session Removal

// Remove session and logout
await bot.logout();
await auth.remove(); // Deletes all session data

// Or just disconnect without removing session
await bot.disconnect();

Credential Updates

The bot automatically saves credentials when they change (from src/core/bot.ts lines 72-79):
this.ws.ev.on("creds.update", async () => {
  try {
    await this.auth.save();
  }
  catch (e) {
    this.emit("error", toError(e));
  }
});
WhatsApp periodically rotates encryption keys. The creds.update event ensures your session stays current without manual intervention.

Multi-Bot Management

Managing multiple bots with different UUIDs:
import { Bot, LocalAuth } from 'wapi';
import { randomUUID } from 'crypto';

const bots = new Map();

// Create multiple bots
for (let i = 0; i < 3; i++) {
  const uuid = randomUUID();
  const auth = new LocalAuth(uuid, './sessions');
  const bot = new Bot(uuid, auth, {
    jid: '',
    pn: `+123456789${i}`,
    name: `Bot ${i + 1}`
  });

  bot.on('open', (account) => {
    console.log(`Bot ${i + 1} connected:`, account.name);
  });

  bots.set(uuid, bot);
  await bot.login('qr');
}

// Each bot has its own isolated session
console.log(`Managing ${bots.size} bots`);

Best Practices

// Store UUID in environment variables for production
const uuid = process.env.BOT_UUID || randomUUID();
try {
  await auth.init();
} catch (error) {
  console.error('Failed to initialize auth:', error);
  // Fallback: create new session
  await auth.remove();
  await auth.init();
}
bot.on('close', async (reason) => {
  if (reason.includes('401') || reason.includes('403')) {
    // Auth error - remove invalid session
    await auth.remove();
  }
});
bot.on('open', async () => {
  console.log('Session valid');
  // Optional: backup session data
});

bot.on('error', (error) => {
  if (error.message.includes('credentials')) {
    console.error('Session corrupted');
  }
});

Next Steps

Bot Architecture

Understand the event-driven architecture

Middleware

Build composable message handlers

Build docs developers (and LLMs) love