https://github.com/green-api/whatsapp-chatbot-js-v2
The WhatsApp chat bot v2 based on the new JavaScript library
https://github.com/green-api/whatsapp-chatbot-js-v2
bot bot-framework botapi whatsapp whatsapp-api whatsapp-bot whatsapp-bot-api whatsapp-chat
Last synced: about 1 month ago
JSON representation
The WhatsApp chat bot v2 based on the new JavaScript library
- Host: GitHub
- URL: https://github.com/green-api/whatsapp-chatbot-js-v2
- Owner: green-api
- License: mit
- Created: 2025-01-27T09:55:34.000Z (over 1 year ago)
- Default Branch: master
- Last Pushed: 2025-10-18T01:13:47.000Z (7 months ago)
- Last Synced: 2025-10-19T01:03:30.923Z (7 months ago)
- Topics: bot, bot-framework, botapi, whatsapp, whatsapp-api, whatsapp-bot, whatsapp-bot-api, whatsapp-chat
- Language: TypeScript
- Homepage: https://green-api.com/en
- Size: 141 KB
- Stars: 1
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# WhatsApp Bot Library
A modern, state-based WhatsApp bot library for Node.js built on top of GREEN-API.
## Support Links
[](mailto:support@green-api.com)
[](https://t.me/greenapi_support_eng_bot)
[](https://wa.me/77273122366)
## Guides & News
[](https://www.youtube.com/@greenapi-en)
[](https://t.me/green_api)
[](https://whatsapp.com/channel/0029VaLj6J4LNSa2B5Jx6s3h)
[](https://www.npmjs.com/package/@green-api/whatsapp-chatbot-js-v2)
[](https://opensource.org/licenses/MIT)
## Features
- State-based conversation flow
- Built-in session management
- Flexible message handling
- Navigation support
- Custom storage extensibility
- TypeScript support
- Automatic instance settings configuration
## Installation
```bash
npm install @green-api/whatsapp-chatbot-js-v2
```
## Quick Start
```typescript
import { WhatsAppBot, State } from '@green-api/whatsapp-chatbot-js-v2';
// Initialize the bot
const bot = new WhatsAppBot({
idInstance: "your-instance-id",
apiTokenInstance: "your-token",
defaultState: "menu"
});
// Define states
const menuState: State = {
name: "menu",
async onEnter(message) {
await bot.sendText(
message.chatId,
"Welcome! Choose an option:\n1. Help\n2. About"
);
},
async onMessage(message) {
if (message.text === "1") {
return "help";
}
return null; // Continue to global handlers
}
};
// Add states and start the bot
bot.addState(menuState);
bot.start();
```
## Core Components
### Bot Configuration
Complete configuration options for the WhatsAppBot:
```typescript
interface BotConfig {
/** Green API instance ID */
idInstance: string;
/** Green API instance token */
apiTokenInstance: string;
/** Initial state name for new sessions. Default: "root" */
defaultState?: string;
/** Session timeout in seconds. Default: 300 (5 minutes) */
sessionTimeout?: number;
/** Function to generate session timeout message based on session data */
getSessionTimeoutMessage?: (session: SessionData) => string;
/** Command(s) to trigger back navigation. Default: "back" */
backCommands?: string | string[];
/** Custom storage adapter for session management. Default: MemoryStorage */
storage?: StorageAdapter;
/** Custom settings for the GREEN-API instance */
settings?: {
webhookUrl?: string;
webhookUrlToken?: string;
outgoingWebhook?: "yes" | "no";
stateWebhook?: "yes" | "no";
incomingWebhook?: "yes" | "no";
outgoingAPIMessageWebhook?: "yes" | "no";
outgoingMessageWebhook?: "yes" | "no";
pollMessageWebhook?: "yes" | "no";
markIncomingMessagesReaded?: "yes" | "no";
};
/** Whether to clear webhook queue on bot startup. Default: false */
clearWebhookQueueOnStart?: boolean;
/** Controls message processing order. When true, global handlers run before state handlers (onMessage).
* When false (default), state handlers run first. */
handlersFirst?: boolean;
}
```
### WhatsAppBot
Main class for creating and managing your bot:
```typescript
const bot = new WhatsAppBot({
// Required parameters
idInstance: "your-instance-id",
apiTokenInstance: "your-token",
// Optional parameters
defaultState: "menu",
sessionTimeout: 30, // 30 seconds
getSessionTimeoutMessage: (session) => {
// Custom logic to return timeout message
return "Your session has expired. Starting over.";
},
backCommands: ["back", "return", "menu"],
storage: new CustomStorage(),
clearWebhookQueueOnStart: true,
// GREEN-API instance settings
settings: {
webhookUrl: "",
webhookUrlToken: "",
outgoingWebhook: "no",
stateWebhook: "no",
incomingWebhook: "yes",
outgoingAPIMessageWebhook: "no",
outgoingMessageWebhook: "no",
pollMessageWebhook: "yes",
markIncomingMessagesReaded: "yes"
}
});
```
### States
States define the conversation flow:
```typescript
interface State {
name: string;
onEnter?: (message, stateData?) => Promise;
onMessage: (message, stateData?) => Promise;
onLeave?: (message, stateData?) => Promise;
}
```
State handler return values:
- `null`: Continue to global handlers
- `undefined`: Stop processing
- `string`: Transition to that state
- `StateTransition`: Transition with data
### Message Handlers
Global message handlers for specific patterns:
```typescript
// Exact text match
bot.onText("help", async (message) => {
await bot.sendText(message.chatId, "Help message");
});
// Regular expression
bot.onRegex(/^order:\s*(\d+)$/, async (message) => {
const [, orderId] = message.text.match(/^order:\s*(\d+)$/);
await bot.sendText(message.chatId, `Order ${orderId} info...`);
});
// Message type
bot.onType("image", async (message) => {
await bot.sendText(message.chatId, "Image received!");
});
```
### Storage
Built-in memory storage with custom adapter support:
```typescript
interface StorageAdapter {
/** Optional event emitter for session-related events like expiration */
events?: StorageEventEmitter;
/** Retrieves session data for a chat */
get(chatId: string): Promise | null>;
/** Stores session data for a chat */
set(chatId: string, data: SessionData): Promise;
/** Optional method to receive session timeout value for cleanup processes */
setSessionTimeout?(timeoutMs: number): void;
}
```
Example custom storage:
```typescript
class CustomStorage implements StorageAdapter {
async get(chatId: string) {
return await db.sessions.findOne({chatId});
}
async set(chatId: string, data: SessionData) {
await db.sessions.upsert({chatId, ...data});
}
}
```
# State System
The bot uses a state-based architecture where each state represents a specific point in the conversation flow. States
can have entry points, message handlers, and exit points.
## State Functions
### onEnter
Called when the bot enters a state. Useful for sending initial messages or setting up state data.
```typescript
const menuState: State = {
name: "menu",
async onEnter(message) {
await bot.sendText(message.chatId, "Welcome to the menu!");
}
};
```
Return values from onEnter:
- `void` - Stay in current state
- `string` - Name of state to transition to
- `StateTransition` - Transition with data:
```typescript
return {
state: "next_state",
data: { someData: "value" },
skipOnEnter: false // Optional: skip next state's onEnter
};
```
### onMessage
Handles messages received while in this state.
```typescript
const orderState: State = {
name: "create_order",
async onMessage(message, data = {items: []}) {
if (message.text === "done") {
return "confirm_order";
}
// Add item to order
data.items.push(message.text);
return {
state: "create_order", // Stay in same state
data: data // Update state data
};
}
};
```
Return values from onMessage:
- `null` - Continue to global handlers (like back command)
- `undefined` - Stop processing
- `string` - Name of state to transition to
- `StateTransition` - Transition with data
### onLeave
Called when leaving a state. Use for cleanup or final processing.
```typescript
const paymentState: State = {
name: "payment",
async onLeave(message, data) {
await savePaymentAttempt(data);
}
};
```
## Session Management
Sessions maintain conversation state and custom data between messages.
### Session Data Structure
```typescript
interface SessionData {
lastActivity: number; // Timestamp of last activity
currentState?: string; // Current state name
stateData?: T; // Custom state data
navigationPath?: string[]; // History of states
previousState?: string; // Last state (for back navigation)
}
```
### State Data
Custom data persists between messages in the same state:
```typescript
interface OrderData {
orderId: string;
items: string[];
total: number;
}
const bot = new WhatsAppBot();
const orderState: State = {
name: "order",
async onMessage(message, data = {items: [], total: 0}) {
// data is typed as OrderData
data.items.push(message.text);
return {state: "order", data};
}
};
```
### Session Timeout
Sessions expire after inactivity (default 300 seconds / 5 minutes). You can customize both the timeout duration and the
message sent when a session times out:
```typescript
const bot = new WhatsAppBot({
// Set timeout to 30 seconds
sessionTimeout: 30,
// Custom timeout message
getSessionTimeoutMessage: (session) => {
// You can access session data to customize the message
const userName = session.stateData?.userName || "User";
return `Hello ${userName}, your session has expired. Starting a new conversation.`;
}
});
```
The built-in memory storage checks for expired sessions every 10 seconds and automatically removes them. If you
implement your own storage adapter, you'll need to implement your own cleanup mechanism.
### Session Timeout Events
Storage adapters can optionally emit events when sessions expire. This feature enables the bot to send timeout
notifications:
```typescript
import { EventEmitter } from 'events';
class DatabaseStorage implements StorageAdapter {
public events = new StorageEventEmitter();
constructor(timeoutSeconds: number) {
// Set up cleanup that emits events
setInterval(() => {
// Find expired sessions
const expiredSessions = // ... your logic
for (const session of expiredSessions) {
this.events.emit('sessionExpired', session.chatId, session);
// Delete session
}
}, 10000);
}
}
```
The events field is optional - if you don't need timeout notifications, you can omit it from your storage
implementation. The timeout message is defined in the getSessionTimeoutMessage function.
## Message Handling Flow
The bot offers two message processing flows, controlled by the `handlersFirst` configuration option:
#### Default Flow (handlersFirst: false)
1. Bot receives message
2. Current state's `onMessage` handler processes message
3. Based on return value:
- If `null`: Global handlers process message (onText, onType, onRegex)
- If `undefined`: Stop processing
- If state name/transition: Enter new state
#### Handlers-First Flow (handlersFirst: true)
1. Bot receives message
2. Global handlers (onText, onRegex, onType) process message
- If a handler returns `true`, continue to state processing
- If a handler returns anything else, stop processing
3. If previous global handler returned `true`, current state's `onMessage` handler processes it
### Handler Return Values
When using the handlers-first approach (`handlersFirst: true`), global message handlers support an additional return
value:
- undefined: Message was handled, stop processing (default)
- true: Message was partially handled, continue to state processing
- anything else: Treat as undefined (stop processing)
## Message Handling Priority (if handlersFirst: false)
Messages are processed in a specific priority order:
1. **State Handler (First)**
- The current state's `onMessage` handler gets the first chance to process every message
- Based on its return value:
- `null`: Continue to global handlers
- `undefined`: Stop processing
- `string`: Transition to that state and stop
- `StateTransition`: Transition with data and stop
2. **Global Handlers (Only if state handler returns null)**
Messages that aren't fully handled by the state (returned `null`) continue to global handlers in this order:
a. **Text Handlers**
- Exact text matches (case-insensitive)
- First matching handler processes the message and stops further handling
b. **Regex Handlers**
- Regular expression pattern matches
- First matching pattern processes the message and stops further handling
c. **Type Handlers**
- Specific type handlers run first
- Generic ("*") handlers run last as fallback
- First matching handler processes the message and stops further handling
This means that if your state's `onMessage` handler returns `undefined` (the default return value), global handlers will
never run. Make sure to return `null` if you want to allow global handlers to process the message.
### Examples of using handlersFirst
#### Configuring the Flow Order
```typescript
const bot = new WhatsAppBot({
idInstance: "your-instance-id",
apiTokenInstance: "your-token",
defaultState: "menu",
handlersFirst: true // Process global handlers before state handlers
});
```
#### Handlers with Different Return Values
```typescript
// Handler that fully processes the message (stops further processing)
bot.onText("/help", async (message) => {
await bot.sendText(message.chatId, "Here's the help information...");
// No explicit return - defaults to undefined (stop processing)
});
// Handler that partially processes the message and continues to state handler
bot.onType("location", async (message) => {
await bot.sendText(message.chatId, "I've received your location...");
return true; // Continue to state processing for additional handling
});
```
## Global Handlers
### Text Handlers
Match exact text (case-insensitive):
```typescript
bot.onText("menu", async (message) => {
await bot.sendText(message.chatId, "Main menu");
});
```
### Regex Handlers
Match text patterns:
```typescript
bot.onRegex(/order:\s*(\d+)/, async (message) => {
const [, orderId] = message.text.match(/order:\s*(\d+)/);
await bot.sendText(message.chatId, `Order ${orderId} details...`);
});
```
### Type Handlers
Handle specific message types:
```typescript
// Handle all images
bot.onType("image", async (message) => {
const url = message.media?.url;
await processImage(url);
});
// Handle all messages (fallback)
bot.onType("*", async (message) => {
console.log("Unhandled message:", message);
});
```
## Custom Storage
Default storage is in-memory, but you can implement custom storage:
```typescript
class DatabaseStorage implements StorageAdapter {
// Optional event support for timeout notifications
public events?: StorageEventEmitter;
constructor(timeoutSeconds: number) {
// Optional: Set up events if you want timeout notifications
this.events = new StorageEventEmitter();
this.startCleanup(timeoutSeconds);
}
private async startCleanup(timeoutSeconds: number) {
setInterval(async () => {
// Your cleanup logic
if (this.events) {
// Emit events for expired sessions
this.events.emit('sessionExpired', chatId, session);
}
}, 10000);
}
async get(chatId: string): Promise {
return await db.sessions.findOne({chatId});
}
async set(chatId: string, data: SessionData): Promise {
await db.sessions.updateOne(
{chatId},
{$set: data},
{upsert: true}
);
}
}
const bot = new WhatsAppBot({
storage: new DatabaseStorage()
});
```
## State Transition Examples
### Simple State Chain
```typescript
const steps: State[] = [
{
name: "step1",
async onMessage(message) {
if (message.text) return "step2";
return undefined;
}
},
{
name: "step2",
async onMessage(message) {
if (message.text) return "step3";
return undefined;
}
}
];
```
### Conditional Transitions
```typescript
const menuState: State = {
name: "menu",
async onMessage(message) {
switch (message.text) {
case "1":
return "orders";
case "2":
return "settings";
default:
return null;
}
}
};
```
### Data Passing Between States
```typescript
interface UserData {
name?: string;
age?: number;
}
const nameState: State = {
name: "get_name",
async onMessage(message, data = {}) {
return {
state: "get_age",
data: {...data, name: message.text}
};
}
};
const ageState: State = {
name: "get_age",
async onMessage(message, data = {}) {
const age = parseInt(message.text);
return {
state: "confirm",
data: {...data, age}
};
}
};
```
## Advanced Features
### Custom State Data
Add type-safe custom data to your states:
```typescript
interface OrderData {
orderId?: string;
items: string[];
total: number;
}
const bot = new WhatsAppBot({...});
const orderState: State = {
name: "create_order",
async onMessage(message, data = {items: [], total: 0}) {
// Type-safe access to custom data
data.items.push(message.text);
return {
state: "confirm_order",
data: data
};
}
};
```
### File Handling
Send different types of files:
```typescript
await bot.sendFile(chatId, {
url: "https://example.com/image.jpg",
type: "image",
caption: "Check this out!"
});
```
## Best Practices
1. **State Organization**
- Keep states focused and single-purpose
- Use clear naming conventions
- Handle edge cases and errors
2. **Error Handling**
- Implement global error handlers
- Log errors appropriately
- Provide user-friendly error messages
3. **Session Management**
- Clean up old sessions regularly
- Implement proper timeout handling
## Examples
### Support Ticket Bot
See `examples/tickets.ts` for a complete example of a support ticket system demonstrating state
management, file handling, and complex conversation flows.
### Custom Storage Implementation
Check `examples/custom-storage/` for an example of implementing a custom storage provider
with a simple bot implementation.
### Demo chatbot example
Check out [our demo chatbot](https://github.com/green-api/whatsapp-demo-chatbot-js-v2) to see a big scale
implementation, based on GREEN-API
## License
MIT