đą WhatsApp Multi-Driver API v1.0
Complete Developer Guide - Multi-Driver WhatsApp Integration
Welcome to the WhatsApp Multi-Driver API documentation. This API provides WhatsApp integration through two drivers: WWebJS (WhatsApp Web.js) and Cloud API (Meta's official Business API). Choose the driver that best fits your use case.
- REST API - Traditional HTTP endpoints for all operations
- MCP (Model Context Protocol) - AI-friendly interface for Claude and other AI agents
đ Quick Start (5 minutes)
Get up and running with the WhatsApp Multi-Driver API in 5 minutes.
Step 1: Get API Key
Contact your system administrator to obtain an API key. The API key is required for all API calls and should be stored securely.
Step 2: Test Connection
// Test API connection
const response = await fetch('https://apiwts.top/health', {
headers: {
'X-API-Key': 'your-api-key-here'
}
});
const data = await response.json();
console.log('API Status:', data.status); // Should be "healthy"
Step 3: Create WhatsApp Session
// Create a new WhatsApp session
const response = await fetch('https://apiwts.top/api/v1/sessions', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key-here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-first-session'
})
});
const session = await response.json();
console.log('Session created:', session);
Step 4: Get QR Code
// Get QR code to scan with WhatsApp
const response = await fetch('https://apiwts.top/api/v1/sessions/my-first-session/qr', {
headers: {
'X-API-Key': 'your-api-key-here'
}
});
const blob = await response.blob();
const qrUrl = URL.createObjectURL(blob);
// Display QR code in an img tag
document.getElementById('qr-code').src = qrUrl;
Step 5: Send Your First Message
// Send WhatsApp message
const response = await fetch('https://apiwts.top/api/v1/messages/text', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key-here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-first-session',
to: '+5511999999999', // International format
text: 'Hello from WhatsApp API! đ'
})
});
const result = await response.json();
console.log('Message sent:', result.id);
â ī¸ CORS Setup - Critical Configuration
Why CORS Matters
- Browser Security: Browsers block direct requests to external domains
- API Protection: External APIs often don't allow all origins
- Development vs Production: Different solutions needed for each environment
Development Solution: Vite Proxy
For development with Vite, use proxy configuration:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'https://apiwts.top',
changeOrigin: true,
secure: false
},
'/health': {
target: 'https://apiwts.top',
changeOrigin: true,
secure: false
}
}
}
});
Code Implementation
// â
CORRECT - Use relative paths (works with proxy)
const response = await fetch('/api/v1/sessions/my-session/qr', {
headers: { 'X-API-Key': 'your-key' }
});
// â WRONG - Direct external URL (CORS error)
const response = await fetch('https://apiwts.top/api/v1/sessions/my-session/qr', {
headers: { 'X-API-Key': 'your-key' }
});
đ Authentication
All API requests require authentication using an API Key in the X-API-Key header.
Getting Your API Key
Contact your system administrator to obtain an API key for your application. Each API key is unique and provides access to the API resources.
- Production:
sk_live_{64-character-hex} - Test:
sk_test_{64-character-hex} - Total Length: 72 characters
- Example:
sk_live_a1b2c3d4e5f6789...(shortened for display)
Security Note: API keys are generated with 256 bits of cryptographic randomness and stored as SHA-256 hashes in the database.
Using the API Key
// Include API Key in all requests
const headers = {
'X-API-Key': 'your-api-key-here',
'Content-Type': 'application/json'
};
const response = await fetch('https://apiwts.top/api/v1/sessions', {
headers
});
đ Driver Comparison
This API supports two WhatsApp integration drivers. Choose based on your requirements:
Available Drivers
| Driver | Type | Best For |
|---|---|---|
| WWebJS WWebJS | WhatsApp Web.js (Puppeteer) | Full feature access, personal/business accounts, no Meta approval needed |
| Cloud API Cloud API | Meta Official Business API | Enterprise scale, template messages, official support, requires Meta approval |
| Instagram Instagram | Meta Graph API (Instagram Messaging) | Instagram DM automation, business accounts, requires Meta App approval (v3.9.0+) |
Feature Compatibility Matrix
| Feature | WWebJS | Cloud API | Notes |
|---|---|---|---|
| Text Messages | â | â | Full support on both |
| Media Messages (Image, Video, Audio, Document) | â | â | Full support on both |
| Template Messages | â | â | Requires Meta-approved templates |
| Message Reactions | â | â | Emoji reactions to messages |
| Message Revoke/Delete | â | â | ~48 hour window |
| Forward Messages | â | â | Forward existing messages |
| Edit Messages | â | â | ~15 min window (v3.4+) |
| Download Media | â | â | Base64 from messages (v3.4+) |
| Send Location | â | â | Geographic coordinates (v3.4+) |
| Get Quoted Message | â | â | Retrieve replied-to message (v3.4+) |
| Star/Unstar Messages | â | â | Favorite/unfavorite messages (v3.4+) |
| Pin/Unpin Messages | â | â | Pin messages in chat for duration (v3.4+) |
| Get Message Mentions | â | â | Get @mentions in a message (v3.4+) |
| Get Poll Votes | â | â | Get votes from poll messages (v3.4+) |
| Message Info (Receipts) | â | â | Detailed delivery/read info |
| Contact Management | â | â | List, block, unblock contacts |
| Chat Management | â | â | Archive, mute, pin, typing indicators |
| Groups (Basic) | â | â | Create, info, add/remove participants |
| Groups (Admin) | â | â | Promote/demote admins, settings |
| Scheduled Messages | â | â | Future message scheduling |
| Webhooks | â | â | Real-time notifications |
| QR Code Authentication | â | â | Cloud API uses phone number verification |
When to Use Each Driver
- You need full feature access (reactions, revoke, contacts, chats)
- You want to use personal or business WhatsApp accounts
- You don't want to go through Meta's approval process
- You need group admin operations
- You need enterprise-scale messaging
- You want to use pre-approved message templates
- You require official Meta support
- Your use case is approved by Meta
Specifying Driver in Requests
When creating a session, specify the driver in the configuration:
// Create session with WWebJS driver (default)
POST /api/v1/sessions
{
"sessionId": "my-session",
"config": {
"driver": "wwebjs"
}
}
// Create session with Cloud API driver
POST /api/v1/sessions
{
"sessionId": "my-cloud-session",
"config": {
"driver": "cloud-api",
"phoneNumberId": "your-phone-number-id",
"accessToken": "your-meta-access-token"
}
}
- WWebJS - Available only with WWebJS driver
- Cloud API - Available only with Cloud API driver
- Instagram - Available only with Instagram driver (v3.9.0+)
- Both - Available with both WhatsApp drivers
đ REST API Reference
Health Check
GET /monitoring/health
Verify API connectivity and status (comprehensive health check).
// Request (no authentication required)
const response = await fetch('https://apiwts.top/monitoring/health');
// Response 200
{
"status": "healthy",
"timestamp": "2025-01-15T10:30:00.000Z",
"uptime": 1186.06,
"version": "1.0.0",
"environment": "production",
"checks": {
"database": "healthy",
"cache": "healthy",
"messageQueue": "healthy",
"sessionManager": "healthy"
},
"memory": {
"used": 123,
"total": 456,
"rss": 789
}
}
GET /monitoring/ready
Kubernetes-style readiness probe - check if application is ready to serve requests.
// Request (no authentication required)
const response = await fetch('https://apiwts.top/monitoring/ready');
// Response 200 - Ready to serve
{
"status": "ready",
"timestamp": "2025-01-15T10:30:00.000Z",
"ready": true,
"checks": {
"database": true,
"cache": true,
"messageQueue": true
}
}
// Response 503 - Not ready
{
"status": "not ready",
"timestamp": "2025-01-15T10:30:00.000Z",
"ready": false,
"checks": {
"database": true,
"cache": false,
"messageQueue": true
}
}
GET /monitoring/live
Kubernetes-style liveness probe - check if application is alive and not deadlocked.
// Request (no authentication required)
const response = await fetch('https://apiwts.top/monitoring/live');
// Response 200 - Alive
{
"status": "alive",
"timestamp": "2025-01-15T10:30:00.000Z",
"alive": true
}
GET /monitoring/metrics
Application metrics - detailed system and application performance data.
// Request (no authentication required)
const response = await fetch('https://apiwts.top/monitoring/metrics');
// Response 200
{
"timestamp": "2025-01-15T10:30:00.000Z",
"system": {
"uptime": 1186.06,
"memory": {
"used": 123,
"total": 456,
"rss": 789,
"external": 12
},
"cpu": {
"user": 5230,
"system": 1240
}
},
"application": {
"sessions": {
"total": 15,
"active": 12,
"byState": {
"READY": 10,
"INITIALIZING": 2,
"AUTHENTICATED": 3
},
"byTenant": {
"tenant-001": 8,
"tenant-002": 7
}
},
"messageQueue": {
"waiting": 50,
"active": 5,
"completed": 1234,
"failed": 12
}
}
}
Admin Diagnostics (Restricted)
⨠NEW in v3.8.13
GET /admin/api/system/diagnostics
Returns system health diagnostics for troubleshooting infrastructure issues.
Responses
- 200 OK: Returns diagnostics summary with overall system status
- 401 Unauthorized: Admin authentication required
- 403 Forbidden: Insufficient permissions
- 503 Service Unavailable: Diagnostics check failed
đ Internal Documentation: Detailed schema, response examples, and troubleshooting guide available in docs/progress/v3.8.13-progress.md.
Session Management
POST /api/v1/sessions
Create a new WhatsApp session.
// Request
const response = await fetch('https://apiwts.top/api/v1/sessions', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001'
})
});
// Response 201
{
"sessionId": "my-session-001",
"state": "INITIALIZING",
"createdAt": "2025-01-15T10:35:00.000Z"
}
GET /api/v1/sessions/:sessionId/qr
Get QR code for WhatsApp authentication.
// Request
const response = await fetch('https://apiwts.top/api/v1/sessions/my-session-001/qr', {
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200 - Binary image (PNG)
const blob = await response.blob();
const qrUrl = URL.createObjectURL(blob);
GET /api/v1/sessions
List all sessions for your tenant.
// Request
const response = await fetch('https://apiwts.top/api/v1/sessions', {
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200
{
"sessions": [
{
"sessionId": "my-session-001",
"state": "READY",
"phoneNumber": "+5511999999999",
"createdAt": "2025-01-15T10:35:00.000Z"
}
]
}
DELETE /api/v1/sessions/:sessionId
Permanently delete a WhatsApp session. This will:
- Remove the session record from the database
- Delete all session authentication files (requires new QR scan)
- Cancel any pending scheduled messages for this session
POST /sessions/:sessionId/restart instead - it disconnects
the WhatsApp connection but preserves the session for reconnection.
// Request - CAUTION: This permanently deletes the session!
const response = await fetch('https://apiwts.top/api/v1/sessions/my-session-001', {
method: 'DELETE',
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 204 - No Content (session permanently deleted)
// Response 404 - Session not found
Session Lifecycle & Operations
Understanding the difference between session operations is crucial:
| Operation | Endpoint | Effect | Recoverable? |
|---|---|---|---|
| Create | POST /sessions |
Creates new session record | - |
| Connect | POST /sessions/:id/connect |
Initializes WhatsApp connection | Yes |
| Restart | POST /sessions/:id/restart |
Disconnects + reconnects (keeps session) | Yes |
| Disconnect v3.7.5+ | POST /sessions/:id/disconnect |
Temporarily disconnects (preserves auth files) | Yes |
| Reconnect v3.7.5+ | POST /sessions/:id/reconnect |
Reconnects a disconnected session | Yes |
| Delete | DELETE /sessions/:id |
PERMANENTLY removes session | NO |
/disconnect for temporary disconnections (e.g., "Disconnect WhatsApp" buttons),
then /reconnect when ready to reconnect. Use /restart for quick reconnection without state change.
Only use DELETE when you truly want to remove the session permanently.
Dangerous Operations IRREVERSIBLE
The following operations are IRREVERSIBLE:
DELETE /api/v1/sessions/:sessionId- Permanently deletes session and all authentication filesDELETE /api/v1/scheduled-messages/:messageId- Permanently cancels scheduled message
Always implement a confirmation dialog in your UI before calling these endpoints.
For temporary disconnection, use POST /sessions/:sessionId/disconnect instead of DELETE.
Example: Temporarily Disconnect WhatsApp
To disconnect a WhatsApp session without deleting it (e.g., for a "Disconnect" button in your UI):
// â
CORRECT: Disconnect temporarily (session preserved)
const disconnectWhatsApp = async (sessionId, apiKey) => {
const response = await fetch(`https://apiwts.top/api/v1/sessions/${sessionId}/disconnect`, {
method: 'POST',
headers: { 'X-API-Key': apiKey }
});
if (response.ok) {
const data = await response.json();
console.log(`Session ${data.sessionId} disconnected`);
// User can reconnect later using /reconnect endpoint
}
};
// To reconnect later:
const reconnectWhatsApp = async (sessionId, apiKey) => {
const response = await fetch(`https://apiwts.top/api/v1/sessions/${sessionId}/reconnect`, {
method: 'POST',
headers: { 'X-API-Key': apiKey }
});
// Check /qr endpoint if new QR code is needed
};
// â WRONG: This DELETES the session permanently!
// await fetch(`/api/v1/sessions/${sessionId}`, { method: 'DELETE' });
POST /api/v1/sessions/:sessionId/connect
Connect/Initialize a WhatsApp session to start generating QR codes.
// Request
const response = await fetch('https://apiwts.top/api/v1/sessions/my-session-001/connect', {
method: 'POST',
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 201 - Connection initiated
{
"sessionId": "my-session-001",
"state": "INITIALIZING",
"message": "Session my-session-001 connection initiated",
"timestamp": "2025-01-15T10:35:00.000Z"
}
// Response 200 - Already connected
{
"sessionId": "my-session-001",
"state": "READY",
"message": "Session my-session-001 already connected",
"timestamp": "2025-01-15T10:35:00.000Z"
}
GET /api/v1/sessions/:sessionId/status
Get session status and QR code availability.
// Request
const response = await fetch('https://apiwts.top/api/v1/sessions/my-session-001/status', {
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200
{
"sessionId": "my-session-001",
"state": "READY",
"qrAvailable": false,
"lastQrAt": "2025-01-15T10:30:00.000Z",
"retries": 0,
"metadata": {}
}
POST /api/v1/sessions/:sessionId/restart
Restart a WhatsApp session (useful for reconnection after errors).
// Request
const response = await fetch('https://apiwts.top/api/v1/sessions/my-session-001/restart', {
method: 'POST',
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200
{
"message": "Session my-session-001 restart initiated",
"timestamp": "2025-01-15T10:35:00.000Z"
}
GET /api/v1/sessions/:sessionId/health
Get session health and connectivity information.
// Request
const response = await fetch('https://apiwts.top/api/v1/sessions/my-session-001/health', {
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200
{
"sessionId": "my-session-001",
"status": "connected",
"connected": true,
"qrAvailable": false,
"lastSeen": "2025-01-15T10:35:00.000Z",
"driverType": "wwebjs",
"metadata": {},
"timestamp": "2025-01-15T10:35:00.000Z"
}
POST /api/v1/sessions/:sessionId/disconnect v3.7.5+
Temporarily disconnect a WhatsApp session without deleting it. Use this instead of DELETE when you want to disconnect temporarily (e.g., for a "Disconnect" button).
/reconnect to reconnect later.
// Request
const response = await fetch('https://apiwts.top/api/v1/sessions/my-session-001/disconnect', {
method: 'POST',
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200 - Disconnected successfully
{
"sessionId": "my-session-001",
"state": "DISCONNECTED",
"message": "Session has been disconnected. Use /reconnect to reconnect.",
"timestamp": "2025-01-15T10:35:00.000Z"
}
// Response 200 - Already disconnected (idempotent)
{
"sessionId": "my-session-001",
"state": "DISCONNECTED",
"message": "Session is already disconnected",
"timestamp": "2025-01-15T10:35:00.000Z"
}
POST /api/v1/sessions/:sessionId/reconnect v3.7.5+
Reconnect a previously disconnected session. If authentication has expired, a new QR code will be generated.
// Request
const response = await fetch('https://apiwts.top/api/v1/sessions/my-session-001/reconnect', {
method: 'POST',
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200 - Reconnect initiated
{
"sessionId": "my-session-001",
"state": "INITIALIZING",
"message": "Session reconnect initiated. Check /qr for QR code if needed.",
"timestamp": "2025-01-15T10:35:00.000Z"
}
Session Reliability & Persistence (v2.87)
â Enhanced Session Reliability: Version 2.87 introduces automatic recovery mechanisms and improved session persistence during deployments, ensuring 99%+ uptime for all sessions.
đ§ Detached Frame Auto-Recovery
WhatsApp sessions can occasionally experience Puppeteer/Chromium corruption (detached frame errors) after QR code timeouts or authentication failures. The system now automatically detects and recovers from these errors:
- Detection: Health checks monitor for "detached Frame" errors every 60 seconds
- Recovery: Corrupted clients are automatically destroyed and cleaned up
- State Update: Session marked as DISCONNECTED in database (ready for reconnection)
- User Action: Click "Generate QR Code" in Admin Dashboard to reconnect
đĄ Note: This eliminates the need for manual container restarts when sessions become unresponsive. The system self-heals automatically within 1-2 minutes.
đ Blue-Green Session Sync
During zero-downtime deployments, the system now synchronizes session files between the active and target environments:
- Pre-Deployment Sync: All session authentication files copied from active (blue/green) to target environment
- Lock Exclusion: Chromium locks excluded (SingletonLock, SingletonSocket) to prevent conflicts
- Orphan Recovery: Target environment successfully recovers sessions with valid authentication
- Zero QR Generation: No QR codes required during traffic switch (authentication already valid)
đĄ Note: Sessions remain READY (connected) during deployments. No manual intervention required.
â¨ī¸ Session Warm-up
After session sync, the system performs a 30-second warm-up period to initialize orphan recovery before switching traffic:
- Health Check Trigger: Validates all sessions in target environment
- Orphan Recovery: WWebJS detects and reconnects existing session files
- Smoke Tests: Run AFTER warm-up to validate sessions are READY
- Traffic Switch: Only proceeds if all validations pass
đ Expected Uptime
With v2.87 improvements:
- Before v2.87: 40% uptime during deployments (sessions disconnect, require manual QR scan)
- After v2.87 (Phase 1): 95%+ uptime (automatic recovery + session sync)
- Future (Phase 2): 99%+ uptime (shared volume + enhanced lock cleanup)
đ¨ Troubleshooting Session Issues
If a session becomes DISCONNECTED:
- Check logs: Look for "[v2.87] Detached frame detected" messages
- Wait 2 minutes: Auto-recovery runs every 60 seconds (may take up to 2 health check cycles)
- Manual reconnection: If still DISCONNECTED, click "Generate QR Code" in Admin Dashboard
- Fresh QR scan: Scan QR code with WhatsApp mobile app
đĄ Note: If reconnection fails repeatedly, check if your WhatsApp account has been banned or restricted by Meta. Contact support@apiwts.top for assistance.
Messaging
đ Idempotency (Duplicate Prevention) - v3.2+ (Enhanced v3.7.9)
To prevent duplicate messages during retries or network failures, ALWAYS include the X-Idempotency-Key header in message sending requests.
| Header | Format | TTL | Description |
|---|---|---|---|
X-Idempotency-Key |
Alphanumeric, hyphens, underscores (max 255 chars) | 24 hours | Unique identifier for this request |
Behavior:
- â First request: Message created and queued (HTTP 200)
- â
Retry with SAME key: Returns cached response (HTTP 200, header
X-Idempotency-Replay: true) - đ Concurrent request with SAME key: Returns HTTP 409 Conflict (v3.7.9+)
- â ī¸ Retry with DIFFERENT key: NEW message created (potential duplicate)
- â ī¸ Request WITHOUT key: No idempotency protection (each request creates new message)
HTTP 409 Conflict Response (v3.7.9+):
When a request with the same idempotency key is already being processed, the API returns HTTP 409 to prevent race conditions:
// HTTP 409 Conflict
{
"statusCode": 409,
"error": "Conflict",
"message": "Request with idempotency key \"msg-xxx\" is already being processed",
"retryAfter": 5
}
// Headers:
// Retry-After: 5
Client handling: Wait for retryAfter seconds and retry. The original request will have completed by then.
Recommended format: msg-{uuid-v4} or {client-request-id}
// Example with Idempotency Key (RECOMMENDED)
const response = await fetch('https://apiwts.top/api/v1/messages/text', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json',
'X-Idempotency-Key': 'msg-550e8400-e29b-41d4-a716-446655440000' // â CRITICAL
},
body: JSON.stringify({
sessionId: 'my-session-001',
to: '+5511999999999',
text: 'Hello from WhatsApp API!'
})
});
// Response (first request)
// HTTP 200
{
"id": "msg_1234567890",
"status": "queued"
}
// Response (retry with same key)
// HTTP 200 + Header: X-Idempotency-Replay: true
{
"id": "msg_1234567890", // Same ID - no duplicate created
"status": "queued"
}
POST /api/v1/messages/text
Send text message via WhatsApp.
// Request
const response = await fetch('https://apiwts.top/api/v1/messages/text', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json',
'X-Idempotency-Key': 'msg-' + crypto.randomUUID() // â Recommended
},
body: JSON.stringify({
sessionId: 'my-session-001',
to: '+5511999999999',
text: 'Hello from WhatsApp API!'
})
});
// Response 200
{
"id": "msg_1234567890",
"sessionId": "my-session-001",
"to": "+5511999999999",
"status": "queued",
"timestamp": "2025-01-15T10:35:00.000Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
sessionId |
string | Yes | WhatsApp session identifier |
to |
string | Yes | Recipient phone (E.164 format) or WhatsApp JID (@c.us, @lid, @g.us) |
text |
string | Yes | Message content (max 4096 characters) |
quotedMessageId |
string | No | ID of the message to quote/reply to. The sent message will appear as a reply to this message. WWebJS Only |
linkPreview |
boolean | No | Generate link preview for URLs in the message (default: true) |
To send a message as a reply to another message, include the quotedMessageId parameter with the serialized ID of the message you want to quote. The recipient will see your message with the quoted message displayed above it.
// Reply to a message
{
"sessionId": "my-session",
"to": "5547991246688",
"text": "Yes, I agree with that!",
"quotedMessageId": "false_5547991246688@c.us_3EB0A0B0C1D2E3F4"
}
Important: The quotedMessageId must be in the serialized format: {fromMe}_{remote}_{id}
fromMe: "true" for messages you sent, "false" for received messagesremote: The chat ID (e.g., "5547991246688@c.us")id: The message hash (e.g., "3EB0A0B0C1D2E3F4")
How to get the serializedId: Use the serializedId field from webhook payloads (available since v3.8.18). Example webhook payload:
{
"event": "message.received.text",
"message": {
"id": "3EB0A0B0C1D2E3F4",
"serializedId": "false_5547991246688@c.us_3EB0A0B0C1D2E3F4" // Use this for quotedMessageId
}
}
Note: The message being quoted must be in WhatsApp Web's cache (approximately the last 500 messages in the conversation).
Graceful Degradation (v3.8.20+): If the quoted message is not found in the cache, the API will log a warning and send the message without the quote (the message will still be delivered, just not as a reply). This ensures message delivery is never blocked by cache limitations.
POST /api/v1/messages/media
Send media messages (images, videos, audio, documents).
// Send image by URL
const response = await fetch('https://apiwts.top/api/v1/messages/media', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001',
to: '+5511999999999',
mediaUrl: 'https://example.com/image.jpg',
mimetype: 'image/jpeg',
caption: 'Check out this image!'
})
});
// Send document
const response = await fetch('https://apiwts.top/api/v1/messages/media', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001',
to: '+5511999999999',
mediaUrl: 'https://example.com/document.pdf',
mimetype: 'application/pdf',
filename: 'invoice.pdf',
caption: 'Your invoice is attached'
})
});
| Field | Type | Required | Description |
|---|---|---|---|
sessionId |
string | â | WhatsApp session identifier |
to |
string | â | Recipient phone (international format) |
mediaUrl |
string | * | URL to media file |
mediaBase64 |
string | * | Base64 encoded media |
mimetype |
string | â | MIME type (image/jpeg, video/mp4, etc.) |
caption |
string | â | Media caption (max 1024 chars) |
filename |
string | â | Custom filename for documents |
Supported Media Types:
- Images: JPEG, PNG, WebP (max 16MB)
- Videos: MP4, AVI, MOV (max 16MB)
- Audio: MP3, WAV, AAC, OGG (max 16MB)
- Documents: PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT (max 100MB)
GET /api/v1/messages/:messageId
Get the status of a sent message.
// Request
const response = await fetch('https://apiwts.top/api/v1/messages/msg_abc123', {
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200
{
"id": "msg_abc123",
"sessionId": "my-session-001",
"to": "+5511999999999",
"status": "sent",
"type": "text",
"timestamp": "2025-01-15T10:35:00.000Z",
"driverMessageId": "3EB0XXXXXXXXXXXXX"
}
GET /api/v1/messages
List all messages for the authenticated tenant with pagination.
// Request with filters
const response = await fetch('https://apiwts.top/api/v1/messages?page=1&limit=20&sessionId=my-session-001&status=sent', {
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200
{
"messages": [
{
"id": "msg_abc123",
"sessionId": "my-session-001",
"to": "+5511999999999",
"status": "sent",
"type": "text",
"timestamp": "2025-01-15T10:35:00.000Z",
"driverMessageId": "3EB0XXXXXXXXXXXXX"
}
],
"total": 150,
"page": 1,
"limit": 20
}
| Query Parameter | Type | Default | Description |
|---|---|---|---|
page |
number | 1 | Page number (min: 1) |
limit |
number | 20 | Messages per page (min: 1, max: 100) |
sessionId |
string | - | Filter by session ID (optional) |
status |
string | - | Filter by status: queued, sent, delivered, read, failed (optional) |
â° Scheduled Messages (v2.46)
Schedule WhatsApp messages to be sent at a future date and time. No maximum scheduling limit - you can schedule messages years in advance for use cases like insurance renewals, warranty expirations, annual contracts, etc.
- â
Real-time Stats API: Get pending/sent/failed/total counters via
/messages/scheduled/stats - â Advanced Filters: Filter by tenant, status, date range via query parameters
- â Enhanced Security: HTML sanitization on all user-generated content
- â Enhanced Buffer: Increased from 1min to 2min (120s) to account for network latency + clock skew
- â Improved Error Messages: Detailed timing context with actionable suggestions
- â Better UX: Errors now include exact timestamps, buffer requirements, and suggested valid time
POST /api/v1/messages/scheduled
Schedule a message for future delivery.
// Schedule message 1 year from now (insurance renewal)
const response = await fetch('https://apiwts.top/api/v1/messages/scheduled', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001',
to: '+5511999999999',
text: 'Your car insurance will expire in 30 days. Renew now to avoid coverage gaps!',
scheduledAt: '2026-01-15T10:00:00Z'
})
});
// Response
{
"id": "msg_abc123",
"sessionId": "my-session-001",
"to": "+5511999999999",
"text": "Your car insurance will expire in 30 days...",
"scheduledAt": "2026-01-15T10:00:00.000Z",
"status": "pending",
"createdAt": "2025-01-15T10:00:00.000Z"
}
Error Response (400 Bad Request - Scheduling Too Soon)
v2.55: Enhanced error messages with detailed timing context and actionable suggestions.
// Attempt to schedule message less than 2 minutes in the future
{
"statusCode": 400,
"error": "SchedulingTooSoon",
"message": "Message scheduled too close to current time",
"details": {
"scheduledAt": "2025-10-10T10:01:00Z", // Your requested time
"currentTime": "2025-10-10T10:00:00Z", // Server current time
"minimumScheduleTime": "2025-10-10T10:02:00Z", // Minimum valid time
"minimumBufferSeconds": 120, // Required buffer (2 minutes)
"timeUntilScheduled": "60s", // How early you are
"suggestion": "Schedule at least 120s in the future. Try: 2025-10-10T10:02:00Z"
}
}
// How to fix: Use the suggested timestamp from error.details.minimumScheduleTime
const errorResponse = await response.json();
if (errorResponse.error === 'SchedulingTooSoon') {
// Retry with suggested time
const suggestedTime = errorResponse.details.minimumScheduleTime;
console.log(`Retrying with suggested time: ${suggestedTime}`);
}
| Field | Type | Required | Description |
|---|---|---|---|
sessionId |
string | â | WhatsApp session identifier |
to |
string | â | Recipient phone (E.164 format: +5511999999999) |
text |
string | â | Message content (1-4096 characters) |
scheduledAt |
string (ISO 8601) | â | Future timestamp (min: +2 minutes, no max limit) |
- Minimum: 2 minutes from now (120 seconds)
- Maximum: No limit (can schedule years ahead)
- Processing: Checked automatically every minute
- Session Validation: Relaxed - can schedule even if session disconnected (will auto-retry when reconnected)
- Buffer Reason: Accounts for network latency, clock skew, and processing time
GET /api/v1/messages/scheduled
List scheduled messages with optional filters.
// List all pending scheduled messages for a session
const response = await fetch('https://apiwts.top/api/v1/messages/scheduled?sessionId=my-session-001&status=pending', {
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response
{
"messages": [
{
"id": "msg_abc123",
"sessionId": "my-session-001",
"to": "+5511999999999",
"text": "Insurance renewal reminder",
"scheduledAt": "2026-01-15T10:00:00.000Z",
"status": "pending",
"createdAt": "2025-01-15T10:00:00.000Z"
}
],
"total": 1,
"page": 1,
"limit": 50
}
| Query Parameter | Type | Description |
|---|---|---|
sessionId |
string | Filter by session ID |
status |
string | Filter by status (pending, queued, sent, failed) |
startDate |
ISO 8601 | Filter messages scheduled after this date |
endDate |
ISO 8601 | Filter messages scheduled before this date |
page |
number | Page number (default: 1) |
limit |
number | Results per page (default: 50) |
DELETE /api/v1/messages/scheduled/:id
Cancel a scheduled message (only if status is pending).
// Cancel scheduled message
const response = await fetch('https://apiwts.top/api/v1/messages/scheduled/msg_abc123', {
method: 'DELETE',
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response (200 OK)
{
"success": true,
"message": "Scheduled message cancelled successfully"
}
// Response (404 Not Found - if already sent or not pending)
{
"statusCode": 404,
"error": "Not Found",
"message": "Message not found or cannot be cancelled (already sent or not scheduled)"
}
DELETE /api/v1/messages/:messageId
v2.84+ Revoke/delete a sent message. Supports "delete for me" or "delete for everyone" (within ~48 hours).
Important: Only supported on wwebjs driver. Cloud API driver will return 422 error.
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
type |
string | No | everyone |
Revoke type: me (delete for yourself only) or everyone (delete for all participants) |
Message Status Requirements
Only messages with status sent, delivered, or read can be revoked. Messages with status pending, queued, failed, or revoked will return 400 error.
Time Constraint
WhatsApp allows deleting messages for everyone only within approximately 48 hours of sending. After this period, you can only delete for yourself (type=me).
// Delete message for everyone (default)
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000', {
method: 'DELETE',
headers: {
'X-API-Key': 'your-api-key'
}
});
// Delete message for yourself only
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000?type=me', {
method: 'DELETE',
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response (200 OK - Success)
{
"success": true,
"message": "Message revoked successfully (everyone)",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"status": "revoked",
"revokedAt": "2025-11-23T06:00:00.000Z",
"revokeType": "everyone",
"revokeReason": "User-initiated via API (DELETE /messages/...?type=everyone)"
}
}
// Response (200 OK - Already Revoked - Idempotent)
{
"success": true,
"message": "Message already revoked",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"status": "revoked",
"revokedAt": "2025-11-23T05:55:00.000Z",
"revokeType": "everyone"
}
}
// Response (400 Bad Request - Invalid Status)
{
"statusCode": 400,
"error": "Bad Request",
"message": "Cannot revoke message with status 'pending'. Only 'sent', 'delivered', or 'read' messages can be revoked."
}
// Response (403 Forbidden - Unauthorized)
{
"statusCode": 403,
"error": "Forbidden",
"message": "You are not authorized to revoke this message"
}
// Response (404 Not Found)
{
"statusCode": 404,
"error": "Not Found",
"message": "Message not found"
}
// Response (410 Gone - Time Expired)
{
"statusCode": 410,
"error": "Gone",
"message": "Message revocation time limit expired (can only delete for yourself now)"
}
// Response (422 Unprocessable Entity - Driver Not Supported)
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Message revocation is not supported by this driver. Only WWebJS driver supports deleting sent messages.",
"driver": "cloud-api",
"supportedDrivers": ["wwebjs"]
}
POST /api/v1/messages/:messageId/react
v2.85+ React to a message with an emoji (WWebJS only).
Important: Only supported on wwebjs driver. Cloud API driver will return 422 error.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
emoji |
string | Yes | Any valid Unicode emoji to add reaction, or empty string "" to remove existing reaction (v3.8.32+). Common examples: đ đ â¤ī¸ đ đŽ đĸ đ đĨ â
â đ đ¯. |
// React with thumbs up emoji
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/react', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
emoji: 'đ'
})
});
// Response (200 OK - Success)
{
"success": true,
"message": "Reaction \"đ\" added successfully",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"emoji": "đ",
"reactedAt": "2025-11-23T08:00:00.000Z"
}
}
// Remove reaction (v3.8.32+) - Send empty string
body: JSON.stringify({
emoji: '' // Empty string removes existing reaction
})
// Response (200 OK - Reaction Removed)
{
"success": true,
"message": "Reaction removed successfully",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"emoji": "",
"reactedAt": "2026-01-05T10:30:00.000Z"
}
}
// Response (400 Bad Request - Invalid Status)
{
"statusCode": 400,
"error": "Bad Request",
"message": "Cannot react to message with status 'queued'. Only 'sent', 'delivered', or 'read' messages can receive reactions."
}
// Response (422 Unprocessable Entity - Driver Not Supported)
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Message reactions are not supported by this driver",
"driver": "cloud-api",
"supportedDrivers": ["wwebjs"]
}
POST /api/v1/sessions/:sessionId/messages/:driverMessageId/react
v3.8.17+ WWebJS Only
Add an emoji reaction to a received (inbound) message using the driverMessageId from the webhook payload.
POST /messages/:messageId/react- For sent messages (you have the UUID from the database)POST /sessions/:sessionId/messages/:driverMessageId/react- For received messages (you have the driverMessageId from webhook)
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID (path parameter) |
| driverMessageId | string | Yes | Driver message ID from webhook payload (path parameter) |
| emoji | string | Yes | Emoji to react with, or empty string "" to remove existing reaction (v3.8.32+) |
Finding the driverMessageId
The webhook payload contains two ID formats. Use serializedId for the /react endpoint:
id- Short format (e.g., "3EB0ABC123DEF456789") - for display/logging onlyserializedId- Full format (e.g., "false_55...@c.us_3EB0...") - required for /react endpoint
// Webhook payload example (message.received.text) - v3.8.18+
{
"event": "message.received.text",
"sessionId": "mysession",
"message": {
"id": "3EB0ABC123DEF456789", // Short format (display only)
"serializedId": "false_5547991246688@c.us_3EB0ABC123DEF456789", // <-- USE THIS for /react
"from": "5547991246688@c.us",
"to": "5511999887766@c.us",
"body": "Hello!",
"timestamp": "2026-01-02T10:30:00.000Z"
}
}
// React to an inbound message (use serializedId from webhook)
const response = await fetch('https://apiwts.top/api/v1/sessions/mysession/messages/false_5547991246688@c.us_3EB0ABC123DEF456789/react', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'your-api-key'
},
body: JSON.stringify({
emoji: "thumbs up"
})
});
// Response (200 OK)
{
"success": true,
"message": "Reaction \"thumbs up\" added successfully",
"data": {
"driverMessageId": "false_5547991246688@c.us_3EB0ABC123DEF456789",
"emoji": "thumbs up",
"reactedAt": "2026-01-02T10:31:00.000Z"
}
}
// Response (404 Not Found - Session)
{
"statusCode": 404,
"error": "Not Found",
"message": "Session mysession not found",
"code": "SESSION_NOT_FOUND"
}
// Response (400 Bad Request - Session Not Active)
{
"statusCode": 400,
"error": "Bad Request",
"message": "Session mysession is not active",
"code": "SESSION_NOT_ACTIVE"
}
// Response (404 Not Found - Message Not in Cache)
{
"statusCode": 404,
"error": "Not Found",
"message": "Message not found. It may have been deleted or is no longer in the WhatsApp cache (~500 recent messages per chat).",
"code": "MESSAGE_NOT_FOUND"
}
// Response (422 Unprocessable Entity - Driver Not Supported)
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Message reactions are not supported by this driver",
"driver": "cloud-api",
"supportedDrivers": ["wwebjs"]
}
GET /api/v1/messages/:messageId/info
v2.85+ Get detailed delivery information about a message, including ACK level and group delivery details (WWebJS only).
Important: Only supported on wwebjs driver. Cloud API driver will return 422 error.
ACK Levels
- -1: Error (message failed to send)
- 0: Pending (message queued on client)
- 1: Server (message sent to WhatsApp server)
- 2: Device (message delivered to recipient device)
- 3: Read (message read by recipient)
- 4: Played (voice/video message played by recipient)
// Get message delivery info
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/info', {
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response (200 OK - Individual Chat)
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"ack": 3,
"timestamp": "2025-11-23T08:00:00.000Z"
}
}
// Response (200 OK - Group Chat with Delivery Details)
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"ack": 3,
"timestamp": "2025-11-23T08:00:00.000Z",
"deliveryDetails": {
"deliveredTo": [
{
"contact": "5511999999999@c.us",
"timestamp": "2025-11-23T08:00:05.000Z"
}
],
"readBy": [
{
"contact": "5511999999999@c.us",
"timestamp": "2025-11-23T08:01:00.000Z"
}
]
}
}
}
// Response (422 Unprocessable Entity - Driver Not Supported)
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Message delivery info is not supported by this driver",
"driver": "cloud-api",
"supportedDrivers": ["wwebjs"]
}
POST /api/v1/messages/:messageId/forward
v2.85+ Forward a message to another chat (WWebJS only).
Important: Only supported on wwebjs driver. Cloud API driver will return 422 error.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
targetChatId |
string | Yes | WhatsApp chat ID to forward to (e.g., "5511999999999@c.us") |
// Forward message to another chat
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/forward', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
targetChatId: '5511888888888@c.us'
})
});
// Response (200 OK - Success)
{
"success": true,
"message": "Message forwarded successfully",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"targetChatId": "5511888888888@c.us",
"forwardedAt": "2025-11-23T08:00:00.000Z"
}
}
// Response (400 Bad Request - Invalid Status)
{
"statusCode": 400,
"error": "Bad Request",
"message": "Cannot forward message with status 'failed'. Only 'sent', 'delivered', or 'read' messages can be forwarded."
}
// Response (422 Unprocessable Entity - Driver Not Supported)
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Message forwarding is not supported by this driver",
"driver": "cloud-api",
"supportedDrivers": ["wwebjs"]
}
PATCH /api/v1/messages/:messageId
v3.4+ Edit a sent text message (WWebJS only).
Important: Only supported on wwebjs driver. Cloud API driver will return 422 error. WhatsApp allows editing messages within ~15 minutes of sending.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
newContent |
string | Yes | New text content for the message (max 4096 characters) |
// Edit a sent text message
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000', {
method: 'PATCH',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
newContent: 'Updated message content'
})
});
// Response (200 OK - Success)
{
"success": true,
"message": "Message edited successfully",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"newContent": "Updated message content",
"editedAt": "2025-11-28T10:00:00.000Z"
}
}
// Response (400 Bad Request - Invalid Message Type)
{
"statusCode": 400,
"error": "Bad Request",
"message": "Cannot edit message of type 'media'. Only text messages can be edited.",
"code": "INVALID_MESSAGE_TYPE"
}
// Response (400 Bad Request - Invalid Status)
{
"statusCode": 400,
"error": "Bad Request",
"message": "Cannot edit message with status 'failed'. Only sent/delivered/read messages can be edited.",
"code": "INVALID_MESSAGE_STATE"
}
// Response (410 Gone - Edit Window Expired)
{
"statusCode": 410,
"error": "Gone",
"message": "Message edit window has expired. Messages can only be edited within 15 minutes of sending.",
"code": "MESSAGE_EDIT_WINDOW_EXPIRED"
}
// Response (422 Unprocessable Entity - Driver Not Supported)
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Message editing is not supported by this driver",
"code": "MESSAGE_EDIT_NOT_SUPPORTED",
"driver": "cloud-api",
"supportedDrivers": ["wwebjs"]
}
GET /api/v1/messages/:messageId/media
Download media from a message (WWebJS only).
v3.7.12+ Webhook Integration
Starting in v3.7.12, message.received.media webhooks include a mediaUrl field that points directly to this endpoint. The URL uses a driverMessageId format that is cached for a configurable TTL (default: 5 minutes).
Flow:
- Receive
message.received.mediawebhook withmediaUrl - Call GET on the
mediaUrlwithin TTL window - Receive media as binary response (Content-Type: image/jpeg, audio/ogg, etc.)
TTL Configuration (v3.7.12+)
Media references are cached with configurable TTL:
MEDIA_DOWNLOAD_TTL_SECONDSenv var (default: 300 = 5 minutes)- Range: 60-1800 seconds (1-30 minutes)
- After TTL expires, returns 404 "Media reference expired"
Supported messageId Formats
- UUID:
550e8400-e29b-41d4-a716-446655440000(database lookup) - driverMessageId:
AC99FE4B684B8EE1AF5A6940ED188444(cache lookup from webhook)
Important: Only supported on wwebjs driver. Cloud API provides media URLs directly in webhooks with pre-signed URLs instead.
Authentication: Both X-API-Key: sk_live_... and Authorization: Bearer sk_live_... headers are supported. Use whichever matches your application's standard pattern.
// v3.7.12+: Download media using mediaUrl from webhook
// Webhook payload includes: "mediaUrl": "https://apiwts.top/api/v1/messages/AC99FE4B.../media"
const response = await fetch(webhook.message.mediaUrl, {
headers: {
'X-API-Key': 'sk_live_your_api_key'
// OR: 'Authorization': 'Bearer sk_live_your_api_key'
}
});
// Response (200 OK - Binary media data)
// Headers:
// Content-Type: image/jpeg (or audio/ogg, video/mp4, application/pdf, etc.)
// Content-Disposition: attachment; filename="photo.jpg"
// Content-Length: 245678
// Body: Binary media data
// Alternative: Download using database UUID (original method)
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/media', {
headers: {
'X-API-Key': 'sk_live_your_api_key'
// OR: 'Authorization': 'Bearer sk_live_your_api_key'
}
});
// Response (200 OK - JSON with base64)
{
"success": true,
"message": "Media downloaded successfully",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"base64": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQ...",
"mimetype": "image/jpeg",
"filename": "photo.jpg",
"filesize": 245678
}
}
// Response (400 Bad Request - No Media)
{
"statusCode": 400,
"error": "Bad Request",
"message": "Message does not contain media",
"code": "MESSAGE_HAS_NO_MEDIA"
}
// Response (404 Not Found - Message not in database)
{
"statusCode": 404,
"error": "Not Found",
"message": "Message 550e8400-e29b-41d4-a716-446655440000 not found",
"code": "MESSAGE_NOT_FOUND"
}
// Response (404 Not Found - v3.7.12+ TTL Expired)
{
"statusCode": 404,
"error": "Not Found",
"message": "Media reference expired or not found. The download link has a limited TTL.",
"code": "MEDIA_REFERENCE_EXPIRED"
}
// Response (403 Forbidden - Tenant mismatch)
{
"statusCode": 403,
"error": "Forbidden",
"message": "You do not have permission to access this media",
"code": "FORBIDDEN"
}
// Response (422 Unprocessable Entity - Driver Not Supported)
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Media download is not supported by this driver. Only WWebJS driver supports downloading media from messages. Cloud API provides media URLs directly in webhooks.",
"code": "MEDIA_DOWNLOAD_NOT_SUPPORTED",
"driver": "cloud-api",
"supportedDrivers": ["wwebjs"]
}
POST /api/v1/messages/location
v3.4+ Send a location message with geographic coordinates.
Supported drivers: wwebjs, cloud-api
// Send location message
const response = await fetch('https://apiwts.top/api/v1/messages/location', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001',
to: '5511999999999',
latitude: -23.5505199,
longitude: -46.6333094,
description: 'SÃŖo Paulo, Brazil',
address: 'Av. Paulista, 1578 - Bela Vista'
})
});
// Response (200 OK - Success)
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"driverMessageId": "true_5511999999999@c.us_ABCDEF123456",
"sessionId": "my-session-001",
"to": "5511999999999",
"latitude": -23.5505199,
"longitude": -46.6333094,
"description": "SÃŖo Paulo, Brazil",
"address": "Av. Paulista, 1578 - Bela Vista",
"status": "sent",
"timestamp": "2025-11-28T14:00:00.000Z"
}
// Response (400 Bad Request - Invalid Coordinates)
{
"statusCode": 400,
"error": "Bad Request",
"message": "Latitude must be between -90 and 90"
}
// Response (404 Not Found - Session Not Found)
{
"statusCode": 404,
"error": "Not Found",
"message": "Session my-session-001 not found"
}
// Response (422 Unprocessable Entity - Driver Not Supported)
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Location messages are not supported by this driver",
"code": "LOCATION_SEND_NOT_SUPPORTED",
"driver": "cloud-api",
"supportedDrivers": ["wwebjs"]
}
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
sessionId |
string | Yes | WhatsApp session ID |
to |
string | Yes | Recipient phone number or WhatsApp JID |
latitude |
number | Yes | Latitude (-90 to 90) |
longitude |
number | Yes | Longitude (-180 to 180) |
description |
string | No | Location name/description (max 256 chars) |
address |
string | No | Address text (max 256 chars) |
GET /api/v1/messages/:messageId/quoted
Get the original message that was quoted/replied to in a message. Returns the quoted message content or null if the message is not a reply. Useful for building conversation threads.
// Get quoted message from a reply
const response = await fetch('https://apiwts.top/api/v1/messages/msg-uuid-here/quoted', {
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response (when message is a reply)
{
"messageId": "msg-uuid-here",
"quotedMessage": {
"id": "true_5511999999999@c.us_AAABB...",
"from": "5511888888888@c.us",
"to": "5511999999999@c.us",
"body": "Original message text",
"type": "chat",
"timestamp": "2024-01-15T10:30:00.000Z",
"hasMedia": false
}
}
// Response (when message is NOT a reply)
{
"messageId": "msg-uuid-here",
"quotedMessage": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
quotedMessage.id |
string | WhatsApp message ID of the quoted message |
quotedMessage.from |
string | Sender of the quoted message (WhatsApp JID) |
quotedMessage.to |
string | Recipient of the quoted message (WhatsApp JID) |
quotedMessage.body |
string | Text content of the quoted message |
quotedMessage.type |
string | Message type (chat, image, video, etc.) |
quotedMessage.timestamp |
string | ISO 8601 timestamp when the quoted message was sent |
quotedMessage.hasMedia |
boolean | Whether the quoted message contains media |
POST /api/v1/messages/:messageId/star
Star (favorite) a message. Starred messages appear in the "Starred Messages" section of the chat.
// Star a message
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/star', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response
{
"success": true,
"message": "Message starred successfully",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"starredAt": "2024-01-15T10:30:00.000Z"
}
}
DELETE /api/v1/messages/:messageId/star
Remove star (unfavorite) from a message.
// Unstar a message
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/star', {
method: 'DELETE',
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response
{
"success": true,
"message": "Message unstarred successfully",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"unstarredAt": "2024-01-15T10:30:00.000Z"
}
}
GET /api/v1/messages/:messageId/reactions
Get all emoji reactions on a message. Returns list of reactions with emoji, sender ID, and timestamp. Useful for analyzing engagement and user feedback.
// Get reactions on a message
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/reactions', {
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response
{
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"reactions": [
{
"emoji": "đ",
"senderId": "5511999999999@c.us",
"timestamp": "2024-01-15T10:30:00.000Z"
},
{
"emoji": "â¤ī¸",
"senderId": "5511888888888@c.us",
"timestamp": "2024-01-15T10:31:00.000Z"
}
],
"totalCount": 2
}
POST /api/v1/messages/:messageId/pin
Pin a message in chat for a specified duration. The message will be pinned at the top of the chat for the specified time. Useful for highlighting important announcements or information in groups.
// Pin a message for 1 day (86400 seconds)
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/pin', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
durationSeconds: 86400 // 1 day (min: 60s, max: 2592000s = 30 days)
})
});
// Response
{
"success": true,
"message": "Message pinned successfully",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"pinned": true,
"durationSeconds": 86400
}
}
DELETE /api/v1/messages/:messageId/pin
Unpin a pinned message in chat. Removes the message from the pinned position in the chat.
// Unpin a message
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/pin', {
method: 'DELETE',
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response
{
"success": true,
"message": "Message unpinned successfully",
"data": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"pinned": false
}
}
GET /api/v1/messages/:messageId/mentions
Get all @mentions in a message. Returns list of mentioned contacts with their IDs, names (if available), phone numbers, and whether they are groups. Useful for processing messages that mention users or groups.
// Get mentions in a message
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/mentions', {
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response
{
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"mentions": [
{
"id": "5511999999999@c.us",
"name": "John Doe",
"phoneNumber": "5511999999999",
"isGroup": false
},
{
"id": "5511888888888@c.us",
"phoneNumber": "5511888888888",
"isGroup": false
}
],
"totalCount": 2
}
GET /api/v1/messages/:messageId/poll-votes
Get all votes from a poll message. Returns list of voters with their selected options and vote timestamps. Only works on poll_creation type messages.
// Get votes from a poll message
const response = await fetch('https://apiwts.top/api/v1/messages/550e8400-e29b-41d4-a716-446655440000/poll-votes', {
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response
{
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"votes": [
{
"voter": "5511999999999@c.us",
"selectedOptions": ["Option A", "Option C"],
"interactedAt": "2025-01-15T10:30:00.000Z"
},
{
"voter": "5511888888888@c.us",
"selectedOptions": ["Option B"],
"interactedAt": "2025-01-15T10:35:00.000Z"
}
],
"totalVoters": 2
}
GET /api/v1/messages/scheduled/stats
Get count of scheduled messages by status.
// Get scheduled messages statistics
const response = await fetch('https://apiwts.top/api/v1/messages/scheduled/stats', {
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response
{
"pending": 45,
"queued": 3,
"sent": 1200,
"failed": 8
}
Status Lifecycle
- pending: Scheduled, waiting for execution time
- queued: Execution time reached, enqueued in send-message queue
- sent: Successfully sent via WhatsApp driver
- failed: Failed to send (session deleted, network error, etc.)
Use Cases
- Short-term: Appointment reminders (24h), follow-ups (3 days), marketing campaigns (1 week)
- Long-term: Insurance renewals (1 year), warranty expirations (11 months), annual contracts
- Business: Birthday messages, subscription renewals, seasonal campaigns
- All rate limiting and retry logic applies to scheduled messages
- If session is disconnected at send time, message will be retried automatically when session reconnects
- If session is deleted before send time, message will be marked as
failed - Messages are checked every 1 minute - scheduling accuracy is Âą60 seconds
đ Webhooks - Real-time Notifications
Configure webhooks to receive real-time notifications when messages arrive, status updates occur, and other events happen.
POST /api/v1/webhooks/configure
Set up webhook URL and events.
// Request
const response = await fetch('https://apiwts.top/api/v1/webhooks/configure', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://your-app.com/webhooks/whatsapp',
events: [
'message.received.text',
'message.received.media',
'message.sent',
'message.delivered',
'message.read'
],
secret: 'your-webhook-secret'
})
});
Available Events
message.received.text- Text message receivedmessage.received.media- Media message receivedmessage.received.location- Location message receivedmessage.sent- Message successfully sentmessage.delivered- Message delivered to recipientmessage.read- Message read by recipient (blue ticks)message.status_update- Message status changed (v2.81+, real-time delivery tracking) â¨session.ready- WhatsApp session readysession.disconnected- WhatsApp session disconnected
v2.81+: The API now automatically tracks message delivery status via WhatsApp ACK (acknowledgment) events. Status updates happen in real-time without requiring API polling:
- pending â Message queued, waiting to be sent (gray clock icon)
- sent â Message reached WhatsApp servers (single gray checkmark)
- delivered â Message reached recipient's device (double gray checkmarks)
- read â Message opened by recipient (double blue checkmarks)
- failed â Message delivery failed (red exclamation)
How it works: WWebJS driver listens for message_ack events from WhatsApp and automatically updates the database. If you have webhooks configured for message.status_update, you'll receive real-time notifications whenever status changes occur.
Webhook Payload Example
v2.70.2+: Payload fields are now flattened to top-level for easier consumption. The full nested data is still available in the data field for backward compatibility.
v3.0.3+: Added fromType field to indicate identifier format. See Identifier Formats section below.
v3.4+: Added 11 contextual fields for richer webhook payloads:
isForwarded,forwardingScore- Forwarded message info (score max 127)links- URLs extracted from message bodyauthor- Sender in group messagesisEphemeral- Disappearing message flagduration- Voice/video message duration in secondsvCards- Contact cards array for contact messagesisStarred,isGif,isStatus,hasReaction,broadcast- Message flagsdeviceType- Device that sent the message (web, android, iphone, etc.)
v3.8.21+: Added hasQuotedMsg and quotedMsg fields for quote/reply support.
v3.8.25+: Added quotedMsgId field + improved reliability with fallback mechanism.
hasQuotedMsg- Boolean indicating if message is a reply to another messagequotedMsgId- v3.8.25+ Direct access to quoted message serialized ID (null if not a reply)quotedMsg- Object with quoted message data (null if not a reply or data unavailable):id- Serialized ID of the quoted messagebody- Text content of the quoted message (may be null if unavailable)from- Sender JID of the quoted message (may be null if unavailable)type- Message type (chat, image, video, etc.)timestamp- ISO 8601 timestamp of the quoted message (may be null if unavailable)
// Text message received (v3.4+ format with all contextual fields)
{
"event": "message.received.text",
"timestamp": "2025-01-15T10:35:23.000Z",
"sessionId": "my-session-001",
"message": {
"id": "msg_987654321",
"from": "5511999999999@c.us", // WhatsApp JID format (see Identifier Formats)
"fromType": "phone", // v3.0.3+: 'phone' | 'lid' | 'group' | 'unknown'
"to": "554791246688@c.us",
"timestamp": "2025-01-15T10:35:23.000Z",
"type": "chat",
"body": "Check out this link: https://example.com",
"hasMedia": false,
"isForwarded": false, // v3.4+: Whether message was forwarded
"forwardingScore": 0, // v3.4+: How many times forwarded (0-127)
"links": ["https://example.com"], // v3.4+: URLs extracted from message (optional)
"isGroup": false, // Whether message is from a group
"isEphemeral": false, // v3.4+: Disappearing message
"isStarred": false, // v3.4+: Whether message is starred
"isGif": false, // v3.4+: Whether message is a GIF
"isStatus": false, // v3.4+: Whether message is a status update
"hasReaction": false, // v3.4+: Whether message has reactions
"hasQuotedMsg": false, // v3.8.21+: Whether message is a reply
"broadcast": false, // v3.4+: Whether message was broadcast
"deviceType": "android", // v3.4+: Device type (web, android, iphone, etc.)
"metadata": { // Additional metadata from driver
"chatId": "5511999999999@c.us",
"chatName": "John Doe", // Contact/chat name
"contactName": "John Doe", // Contact name from address book
"hasMedia": false
}
}
}
// Message from Linked Identity (Switzerland, Business accounts, etc.)
// â ī¸ IMPORTANT: @lid identifiers are NOT phone numbers!
{
"event": "message.received.text",
"timestamp": "2025-01-15T10:35:23.000Z",
"sessionId": "my-session-001",
"message": {
"id": "msg_lid_example",
"from": "77442588909785@lid", // Linked Identity (NOT a phone number!)
"fromType": "lid", // v3.0.3+: Use metadata.chatName for contact name
"to": "554791246688@c.us",
"timestamp": "2025-01-15T10:35:23.000Z",
"type": "chat",
"body": "Hello from Switzerland!",
"hasMedia": false,
"metadata": {
"chatId": "77442588909785@lid",
"chatName": "Giovanni Moser", // â Use this for contact identification with @lid
"contactName": "Giovanni Moser",
"hasMedia": false
}
}
}
// Media message received (v3.7.12+: includes mediaUrl for download)
{
"event": "message.received.media",
"timestamp": "2025-01-15T10:36:55.000Z",
"sessionId": "my-session-001",
"message": {
"id": "msg_image_12345",
"from": "5511999999999@c.us",
"fromType": "phone",
"to": "554791246688@c.us",
"timestamp": "2025-01-15T10:36:55.000Z",
"type": "image",
"body": "Check this out!",
"hasMedia": true,
"mediaUrl": "https://apiwts.top/api/v1/messages/AC99FE4B684B8EE1AF5A6940ED188444/media",
"metadata": {
"chatName": "John Doe",
"hasMedia": true
}
}
}
// Group message received (v3.4+ with author and isForwarded)
{
"event": "message.received.text",
"timestamp": "2025-01-15T10:37:00.000Z",
"sessionId": "my-session-001",
"message": {
"id": "msg_group_12345",
"from": "120363220421466993@g.us", // Group JID
"fromType": "group", // v3.0.3+: This is a group message
"to": "554791246688@c.us",
"timestamp": "2025-01-15T10:37:00.000Z",
"type": "chat",
"body": "Hello team!",
"hasMedia": false,
"isGroup": true, // v3.4+: Message is from a group
"author": "5511999999999@c.us", // v3.4+: Actual sender within the group
"isForwarded": true, // v3.4+: This message was forwarded
"forwardingScore": 3, // v3.4+: Forwarded 3 times
"metadata": {
"chatId": "120363220421466993@g.us",
"chatName": "Project Team", // Group name
"hasMedia": false
}
}
}
// Reply/quoted message received (v3.8.21+ with hasQuotedMsg and quotedMsg, v3.8.25+ with quotedMsgId)
{
"event": "message.received.text",
"timestamp": "2026-01-02T11:00:00.000Z",
"sessionId": "my-session-001",
"message": {
"id": "msg_reply_12345",
"serializedId": "false_5547991246688@c.us_3EB0ABC123",
"from": "5547991246688@c.us",
"fromType": "phone",
"to": "554791246688@c.us",
"timestamp": "2026-01-02T11:00:00.000Z",
"type": "chat",
"body": "Yes, I received it!", // The reply message text
"hasMedia": false,
"hasQuotedMsg": true, // v3.8.21+: This is a reply to another message
"quotedMsgId": "false_554791246688@c.us_3EB0DEF456", // v3.8.25+: Direct access to quoted ID
"quotedMsg": { // v3.8.21+: The original message being quoted
"id": "false_554791246688@c.us_3EB0DEF456",
"body": "Did you receive the document?",
"from": "554791246688@c.us",
"type": "chat",
"timestamp": "2026-01-02T10:55:00.000Z"
},
"metadata": {
"chatId": "5547991246688@c.us",
"chatName": "John Doe",
"contactName": "John Doe",
"hasMedia": false
}
}
}
// Voice message received (v3.4+ with duration, v3.7.12+ with mediaUrl)
{
"event": "message.received.media",
"timestamp": "2025-01-15T10:38:00.000Z",
"sessionId": "my-session-001",
"message": {
"id": "msg_voice_12345",
"from": "5511999999999@c.us",
"fromType": "phone",
"to": "554791246688@c.us",
"timestamp": "2025-01-15T10:38:00.000Z",
"type": "ptt", // ptt = push-to-talk (voice message)
"hasMedia": true,
"mediaUrl": "https://apiwts.top/api/v1/messages/B7C8D9E0F1234567890123456789ABCD/media",
"duration": 15, // v3.4+: Duration in seconds
"deviceType": "iphone", // v3.4+: Sent from iPhone
"metadata": {
"chatName": "John Doe",
"hasMedia": true
}
}
}
// Contact card message received (v3.4+ with vCards)
{
"event": "message.received.contact",
"timestamp": "2025-01-15T10:39:00.000Z",
"sessionId": "my-session-001",
"message": {
"id": "msg_contact_12345",
"from": "5511999999999@c.us",
"fromType": "phone",
"to": "554791246688@c.us",
"timestamp": "2025-01-15T10:39:00.000Z",
"type": "vcard",
"hasMedia": false,
"vCards": [ // v3.4+: Contact cards in vCard format
"BEGIN:VCARD\nVERSION:3.0\nFN:Jane Smith\nTEL:+5511888888888\nEND:VCARD"
],
"metadata": {
"chatName": "John Doe",
"hasMedia": false
}
}
}
// Message status update (v2.81+ - automatic tracking)
{
"event": "message.status_update",
"timestamp": "2025-11-21T15:45:32.000Z",
"sessionId": "claudecode",
"message": {
"id": "9a0c43b4-9416-4133-ae5e-b768c8ac8839", // Internal message ID
"driverMessageId": "3EB0F0E7C8D4F1234567890ABCDEF", // WhatsApp message ID
"status": "delivered", // New status: sent â delivered â read
"updatedAt": "2025-11-21T15:45:32.000Z" // When status changed
},
"timestamp": "2025-11-21T15:45:32.000Z"
}
// Status lifecycle example (3 separate webhook events for same message):
// 1. status: "sent" - Message reached WhatsApp servers
// 2. status: "delivered" - Message reached recipient's device
// 3. status: "read" - Recipient opened message
// Message send failed (v3.7.0+ - explicit failure detection)
{
"event": "message.failed",
"timestamp": "2025-12-02T14:30:45.000Z",
"sessionId": "claudecode",
"message": {
"id": "6257fd0f-d62a-4587-86fa-90583aeb95d3",
"recipient": "41764791479@c.us",
"error": "Driver returned failed status or empty driverMessageId",
"failedAt": "2025-12-02T14:30:45.000Z",
"attempt": 5,
"maxAttempts": 5
}
}
// Message sent successfully (v3.8.12+ - includes body, from, type)
// Use case: Lead Automation - detect patterns in self-sent messages
{
"event": "message.sent",
"timestamp": 1734523456789,
"messageId": "9a0c43b4-9416-4133-ae5e-b768c8ac8839",
"sessionId": "cloudagent",
"from": "5547991246688", // v3.8.12+: Sender phone number
"to": "5511999999999",
"type": "text", // v3.8.12+: Message type (text, media, template, etc.)
"body": "đ Nova Oportunidade\nLead: JoÃŖo Silva", // v3.8.12+: Message content
"hasMedia": false, // v3.8.12+: Whether message contains media
"driverMessageId": "3EB0F0E7C8D4F1234567890ABCDEF",
"status": "sent"
}
// Media message sent (v3.8.12+)
{
"event": "message.sent",
"timestamp": 1734523500000,
"messageId": "b2c3d4e5-f678-9012-3456-789abcdef012",
"sessionId": "cloudagent",
"from": "5547991246688",
"to": "5511999999999",
"type": "media",
"body": "Photo caption here", // Caption for media, empty string if none
"hasMedia": true,
"driverMessageId": "3EB0F0E7C8D4F9876543210FEDCBA",
"status": "sent"
}
// Message reaction received (v2.90+)
// Triggered when someone reacts to a message with an emoji
{
"event": "message.reaction",
"timestamp": "2025-01-01T10:00:00.000Z",
"sessionId": "my-session",
"message": {
"messageId": "3EB0A0B0C1D2E3F4", // ID of the message that received the reaction
"from": "5547991246688@c.us", // WhatsApp ID of the person who reacted
"reaction": "đ", // The emoji used (empty string if reaction removed)
"reactedAt": "2025-01-01T10:00:00.000Z" // When the reaction was added
}
}
// Reaction removed (v2.90+)
// When a user removes their reaction, the emoji field is empty
{
"event": "message.reaction",
"timestamp": "2025-01-01T10:05:00.000Z",
"sessionId": "my-session",
"message": {
"messageId": "3EB0A0B0C1D2E3F4",
"from": "5547991246688@c.us",
"reaction": "", // Empty string = reaction removed
"reactedAt": "2025-01-01T10:05:00.000Z"
}
}
The message.reaction webhook is triggered when someone reacts to a message with an emoji.
| Field | Type | Description |
|---|---|---|
messageId |
string | ID of the message that received the reaction |
from |
string | WhatsApp ID of the person who reacted (@c.us or @lid format) |
reaction |
string | The emoji used. Empty string ("") when reaction is removed. |
reactedAt |
string | ISO 8601 timestamp when the reaction was added/removed |
v3.8.12+: The message.sent webhook now includes additional fields for richer integrations:
from- Sender phone number (E.164 format without +)type- Message type: text, media, template, interactive, listbody- Message content (text for text messages, caption for media)hasMedia- Boolean indicating if message contains media
Use Case: Lead Automation
With the new body field, you can now detect patterns in self-sent messages to trigger automation flows:
// Example: Detect "Nova Oportunidade" pattern for Lead Automation
app.post('/webhooks/whatsapp', (req, res) => {
const { event, body, from, sessionId } = req.body;
if (event === 'message.sent' && body?.includes('đ Nova Oportunidade')) {
// Trigger Lead Automation workflow
triggerLeadWorkflow({
source: 'whatsapp',
sessionId,
senderPhone: from,
messageBody: body
});
}
res.status(200).send('OK');
});
v3.7.0+: The MessageWorker now explicitly verifies send results before marking messages as sent:
- Before v3.7.0: If WWebJS
sendMessage()failed silently (returned emptydriverMessageId), messages were incorrectly marked as "sent" - After v3.7.0: Messages are only marked as "sent" if they receive a valid
driverMessageIdfrom WhatsApp
Retry Behavior: Failed messages are retried up to 5 times with exponential backoff. After max retries, they go to the Dead Letter Queue (DLQ) and a message.failed webhook is dispatched.
Common Failure Causes:
- Invalid recipient number (doesn't exist on WhatsApp)
- Number format mismatch (@c.us vs @lid)
- Session disconnected during send
- WhatsApp rate limiting
Webhook Security (HMAC)
Webhooks include an X-Signature header with HMAC-SHA256 signature (base64-encoded) for verification.
Signature Header: X-Signature: <base64-encoded-hmac>
HMAC Secret: Your tenant's API key (or webhook secret if configured)
// Node.js example - Verify webhook signature
import crypto from 'crypto';
const verifyWebhookSignature = (payloadString, receivedSignature, secret) => {
// Generate expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payloadString)
.digest('base64'); // v2.8+ uses base64 encoding
// Compare signatures (timing-safe)
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);
};
// Express.js webhook endpoint
app.post('/webhooks/whatsapp', express.json(), (req, res) => {
const receivedSignature = req.headers['x-signature'];
const payloadString = JSON.stringify(req.body);
// Verify signature using your API key or webhook secret
if (!verifyWebhookSignature(payloadString, receivedSignature, process.env.API_KEY)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Signature valid - process webhook
const event = req.body;
console.log('Verified webhook:', event.event, 'from:', event.from);
res.status(200).send('OK');
});
// Fastify example
fastify.post('/webhooks/whatsapp', async (request, reply) => {
const receivedSignature = request.headers['x-signature'];
const payloadString = JSON.stringify(request.body);
if (!verifyWebhookSignature(payloadString, receivedSignature, process.env.API_KEY)) {
return reply.status(401).send({ error: 'Invalid signature' });
}
const event = request.body;
console.log('Webhook received:', event.event, event.from, 'â', event.to);
return { status: 'ok' };
});
WhatsApp Identifier Formats - v3.0.3+ đ
The from field in webhooks uses WhatsApp JID (Jabber ID) format. Understanding these formats is crucial for proper message handling.
â ī¸ Important: @lid Format
The @lid format (Linked Identity) is NOT a phone number. Use metadata.chatName to identify the contact. This format is common in Switzerland, some European countries, and WhatsApp Business accounts.
Identifier Types
| Format | fromType | Example | Description | Contains Phone? |
|---|---|---|---|---|
@c.us |
"phone" |
5547999999999@c.us |
Traditional individual chat | â Yes - can be normalized to E.164 |
@lid |
"lid" |
77442588909785@lid |
Linked Identity (internal ID) | â No - use metadata.chatName |
@g.us |
"group" |
120363220421466993@g.us |
Group chat | â No - this is a group ID |
Processing by fromType (v3.0.3+)
// TypeScript example - Handle different identifier types
interface WebhookPayload {
event: string;
sessionId: string;
message: {
from: string;
fromType: 'phone' | 'lid' | 'group' | 'unknown'; // v3.0.3+
metadata?: {
chatName?: string;
contactName?: string;
};
};
}
function extractContactInfo(webhook: WebhookPayload) {
const { from, fromType, metadata } = webhook.message;
switch (fromType) {
case 'phone':
// Traditional format - normalize to E.164
const phone = from.replace('@c.us', '');
return {
type: 'phone',
identifier: '+' + phone,
displayName: metadata?.contactName || metadata?.chatName || phone
};
case 'lid':
// Linked Identity - use chatName for identification
return {
type: 'lid',
identifier: from, // Keep as-is (internal ID)
displayName: metadata?.chatName || 'Unknown Contact'
};
case 'group':
// Group message - extract author for actual sender
return {
type: 'group',
groupId: from,
displayName: metadata?.chatName || 'Unknown Group'
};
default:
return {
type: 'unknown',
identifier: from,
displayName: metadata?.chatName || from
};
}
}
Phone Number Normalization (for @c.us only)
// Only normalize @c.us format to E.164
function normalizePhoneNumber(jid: string): string | null {
if (!jid.endsWith('@c.us')) {
return null; // Cannot normalize @lid or @g.us
}
const phone = jid.replace('@c.us', '');
return '+' + phone;
}
// Examples:
normalizePhoneNumber('5547999999999@c.us') // â '+5547999999999'
normalizePhoneNumber('77442588909785@lid') // â null (not a phone!)
normalizePhoneNumber('120363...@g.us') // â null (group ID)
Sending Messages to @lid Contacts - v3.0.4+ đ
Starting from v3.0.4, you can send messages using the complete JID format in the to field. This is essential for replying to contacts who appear with @lid format.
đĄ Key Insight
When you receive a message from 77442588909785@lid, you cannot simply use the numeric portion as a phone number. You must send the reply using the complete JID including the @lid suffix.
Accepted Formats for to Field
| Format | Example | Use Case |
|---|---|---|
| E.164 (phone) | 5547999999999 |
Traditional contacts - converted to @c.us internally |
| JID @c.us | 5547999999999@c.us |
Explicit contact JID (used as-is) |
| JID @lid | 77442588909785@lid |
Linked Identity contacts â ī¸ (used as-is) |
| JID @g.us | 120363220421466993@g.us |
Send to groups (used as-is) |
Example: Replying to an @lid Contact
// Step 1: You receive a webhook with @lid format
{
"event": "message.received.text",
"sessionId": "sensyxboutique",
"message": {
"from": "77442588909785@lid", // â Note the @lid format
"fromType": "lid",
"body": "OlÃĄ!",
"metadata": {
"chatName": "Giovanni Moser" // â Use this to identify the contact
}
}
}
// Step 2: Reply using the COMPLETE JID
POST /api/v1/messages/text
{
"sessionId": "sensyxboutique",
"to": "77442588909785@lid", // â Use the complete JID from webhook
"text": "OlÃĄ! Bem-vindo à Sensyx Boutique!"
}
â ī¸ Common Mistake
Wrong: "to": "77442588909785" â Message queued but NOT delivered (wrong format)
Correct: "to": "77442588909785@lid" â Message delivered successfully â
@lid Limitations
- No phone number: Cannot be converted to E.164 format
- Identification: Use
metadata.chatNameto identify the contact - Storage: Store the complete @lid JID in your database for future communication
- Regional: Common in Switzerland, some European countries, and WhatsApp Business accounts
Best Practice: Store the JID
// When receiving a message, store the complete JID
async function handleIncomingMessage(webhook: WebhookPayload) {
const { from, fromType, metadata } = webhook.message;
// Store the complete JID for future replies
await db.contacts.upsert({
jid: from, // "77442588909785@lid" or "5547999999999@c.us"
type: fromType, // "lid" or "phone"
displayName: metadata?.chatName, // "Giovanni Moser"
sessionId: webhook.sessionId
});
}
// When sending a reply, use the stored JID
async function sendReply(contactId: string, text: string) {
const contact = await db.contacts.findById(contactId);
await api.post('/api/v1/messages/text', {
sessionId: contact.sessionId,
to: contact.jid, // Use stored JID directly
text: text
});
}
Webhook Authentication (Bearer Token) - v2.86+
Configure Bearer token authentication for secure webhook delivery to protected endpoints (Supabase Edge Functions, AWS Lambda, Azure Functions, etc.).
đ Security Requirements
- HTTPS Required: Webhook URL must use HTTPS when auth token is configured
- Token Length: Minimum 20 characters, maximum 500 characters
- Storage: Tokens are stored encrypted in database
- Logging: Only first 8 characters logged (security)
When to Use Bearer Token Authentication
Bearer tokens are required when your webhook endpoint needs authentication:
- Supabase Edge Functions: Requires
anonorservice_rolekey - AWS Lambda + API Gateway: JWT authorizer tokens
- Azure Functions: Function keys (
x-functions-keyorAuthorization) - Custom APIs: Any endpoint requiring
Authorization: Bearer <token>
Configuration via Admin Dashboard (v2.88: Token Generation UI)
Generate and configure secure webhook tokens directly in the Admin Dashboard:
⨠NEW in v2.88: Automatic Token Generation
The Admin Dashboard now includes a "Gerar Token Seguro" button that automatically generates cryptographically secure tokens (40+ characters) with proper format: wh_sessao_20251123_{random}
Step-by-Step: Generate Token
- Navigate to Sessions tab in Admin Dashboard
- Click Create Session or edit existing session
- Fill in Session ID (required for token generation)
- Fill in Webhook URL (must be HTTPS)
- Click "đ Gerar Token Seguro" button - Token will be auto-generated and populated
- (Optional) Click đ Copy button to copy token to clipboard
- (Optional) Click đī¸ Mostrar to toggle token visibility
- Save session
Token Format (v2.88)
Auto-generated tokens follow this secure format:
wh_{sessionId}_{YYYYMMDD}_{20-random-chars}
Example:
wh_sensyx_20251123_a7f3k9j2m1n4p6q8r0s2t4u6v8w0x2y4z6
âââŦâââ âââââŦââââ âââââââââââââââŦâââââââââââââââââ
â â ââ 20 crypto-secure random chars
â ââ Creation date (YYYYMMDD)
ââ Session identifier (prefix: wh_)
Manual Token Entry (Alternative)
You can also manually enter tokens (e.g., Supabase anon key, custom JWT):
- Paste token into Webhook Auth Token field
- Ensure token is minimum 20 characters
- Save session
Configuration via API
// Create session with webhook authentication
const response = await fetch('https://apiwts.top/admin/api/sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': 'session=...' // Admin authentication
},
body: JSON.stringify({
tenantId: 'your-tenant-id',
sessionId: 'my-session',
webhookUrl: 'https://your-edge-function.supabase.co/webhook',
webhookAuthToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' // Your Bearer token
})
});
// Update existing session
const updateResponse = await fetch('https://apiwts.top/admin/api/sessions/my-session', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Cookie': 'session=...'
},
body: JSON.stringify({
webhookUrl: 'https://new-endpoint.com/webhook',
webhookAuthToken: 'your-new-token' // Leave empty to keep existing
})
});
How apiwts.top Sends Authenticated Webhooks
When webhookAuthToken is configured, apiwts.top includes the Authorization header:
POST /webhook HTTP/1.1
Host: your-edge-function.supabase.co
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Webhook-Event: message.received
X-Message-ID: msg_123456789
X-Signature: abc123... (HMAC signature, if secret configured)
User-Agent: WhatsApp-Multi-Driver-API/1.0
{
"event": "message.received",
"sessionId": "my-session",
"from": "5551234567890",
"body": "Hello World",
"timestamp": 1730000000000
}
Example: Supabase Edge Function
// Supabase Edge Function (TypeScript)
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
serve(async (req) => {
// 1. Verify Authorization header
const authHeader = req.headers.get('Authorization');
const expectedToken = 'Bearer ' + Deno.env.get('SUPABASE_ANON_KEY');
if (authHeader !== expectedToken) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
// 2. Process webhook
const webhook = await req.json();
console.log('Webhook received:', webhook.event, webhook.from);
// Your business logic here...
return new Response(JSON.stringify({ status: 'ok' }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
});
Example: Express.js with Bearer Token
// Express.js endpoint (Node.js)
app.post('/webhook', express.json(), (req, res) => {
// 1. Verify Bearer token
const authHeader = req.headers.authorization;
const expectedToken = 'Bearer ' + process.env.WEBHOOK_TOKEN;
if (authHeader !== expectedToken) {
return res.status(401).json({ error: 'Unauthorized' });
}
// 2. Optional: Verify HMAC signature (if configured)
const signature = req.headers['x-signature'];
if (signature) {
const payloadString = JSON.stringify(req.body);
if (!verifyWebhookSignature(payloadString, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
}
// 3. Process webhook
const event = req.body;
console.log('Authenticated webhook:', event.event, event.from);
res.status(200).send('OK');
});
Example: AWS Lambda + API Gateway
// Lambda function (Node.js)
export const handler = async (event) => {
// API Gateway JWT Authorizer already validated token
// event.requestContext.authorizer.claims contains JWT claims
const webhook = JSON.parse(event.body);
console.log('Webhook received:', webhook.event, webhook.from);
// Your business logic here...
return {
statusCode: 200,
body: JSON.stringify({ status: 'ok' })
};
};
Error Handling & Retry Logic
apiwts.top implements intelligent retry logic for webhook deliveries:
- 401 Unauthorized: Invalid/expired token - NO RETRY (fix token configuration)
- 403 Forbidden: Valid token but insufficient permissions - NO RETRY (check token permissions)
- 408 Request Timeout: Retries up to 3 times (1s, 5s, 30s intervals)
- 429 Too Many Requests: Retries up to 3 times
- 5xx Server Errors: Retries up to 3 times (temporary failures)
- Other 4xx Errors: NO RETRY (client errors are permanent)
Troubleshooting
â ī¸ Common Issues
-
HTTP 401 "Webhook URL must use HTTPS when auth token is configured"
Solution: Change webhook URL fromhttp://tohttps:// -
Webhooks not arriving at Supabase Edge Function
Solution: ConfigurewebhookAuthTokenwith your Supabase anon key -
Webhook delivers but returns 401 Unauthorized
Solution: Verify token is correct and matches your endpoint's expected token -
Token rejected: "must be at least 20 characters"
Solution: Use a secure token with minimum 20 characters (recommended: 32+ chars)
â Identifier Format Issues (v3.0.3+)
-
"Invalid phone format" error when processing webhooks
Cause: Code is trying to normalize@lidformat as a phone number.
Solution: CheckfromTypefield before normalizing. Only normalize whenfromType === "phone". -
Messages from some contacts not appearing in your system
Cause: Validation filters are rejecting@lidformat as invalid.
Solution: Accept all three formats (@c.us,@lid,@g.us). UsefromTypeto differentiate. -
Contact name shows as "Unknown" for @lid messages
Cause: Code is parsing thefromfield expecting a phone number.
Solution: For@lidformat, usemetadata.chatNameormetadata.contactNamefor the contact's display name. -
Swiss/European contacts always show @lid instead of phone number
Cause: This is expected behavior. Some WhatsApp accounts (especially Business accounts and certain regions) use Linked Identity.
Solution: Store both thefrom(identifier) andmetadata.chatName(display name) in your database.
Security Best Practices
- Use HTTPS: Always use
https://webhook URLs when auth tokens are configured - Rotate Tokens: Regularly rotate Bearer tokens (recommended: every 90 days)
- Minimum Length: Use tokens with at least 32 characters for security
- Environment Variables: Store tokens in environment variables, never hardcode
- Dual Authentication: Use both Bearer token + HMAC signature for maximum security
- Monitor Failures: Check webhook delivery logs for authentication failures
Webhook Headers
apiwts.top sends these headers with every webhook delivery:
Content-Type: application/jsonX-Webhook-Event: message.received- Event typeX-Message-ID: msg_987654321- Message identifierX-Signature: <base64-hmac>- HMAC signature for verificationUser-Agent: WhatsApp-Multi-Driver-API/1.0
GET /api/v1/webhooks/config
Retrieve current webhook configuration for your tenant.
// Request
const response = await fetch('https://apiwts.top/api/v1/webhooks/config', {
method: 'GET',
headers: {
'X-API-Key': 'your-api-key'
}
});
const config = await response.json();
console.log(config);
// Response (200 OK)
{
"id": "webhook_config_abc123",
"tenantId": "tenant_xyz789",
"url": "https://your-app.com/webhooks/whatsapp",
"events": [
"message.received.text",
"message.received.media",
"message.sent"
],
"retryCount": 3,
"timeoutMs": 10000,
"active": true,
"createdAt": "2025-10-01T10:00:00.000Z",
"updatedAt": "2025-10-01T10:00:00.000Z"
}
// Response (404 Not Found) - No webhook configured
{
"statusCode": 404,
"error": "Not Found",
"message": "No webhook configuration found for this tenant"
}
DELETE /api/v1/webhooks/config
Remove webhook configuration (soft delete - sets active = false).
// Request
const response = await fetch('https://apiwts.top/api/v1/webhooks/config', {
method: 'DELETE',
headers: {
'X-API-Key': 'your-api-key'
}
});
// Response (200 OK)
{
"message": "Webhook configuration deleted successfully"
}
// Response (404 Not Found)
{
"statusCode": 404,
"error": "Not Found",
"message": "No webhook configuration found for this tenant"
}
GET /api/v1/webhooks/events
List all available webhook event types that can be subscribed to.
⨠Updated in v2.90 - 22 webhook events now available
// Request
const response = await fetch('https://apiwts.top/api/v1/webhooks/events', {
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200 (22 events total)
{
"events": [
// Message Events (11 types)
{ "type": "message.received.text", "description": "Text message received from contact", "category": "messages" },
{ "type": "message.received.media", "description": "Media message (image/video/audio/document) received", "category": "messages" },
{ "type": "message.received.location", "description": "Location message received", "category": "messages" },
{ "type": "message.received.contact", "description": "Contact card/vCard received", "category": "messages" },
{ "type": "message.sent", "description": "Message successfully sent via WhatsApp", "category": "messages" },
{ "type": "message.delivered", "description": "Message delivered to recipient", "category": "messages" },
{ "type": "message.read", "description": "Message read by recipient (blue ticks)", "category": "messages" },
{ "type": "message.failed", "description": "Message failed to send", "category": "messages" },
{ "type": "message.edited", "description": "Message was edited (v2.90+)", "category": "messages" },
{ "type": "message.reaction", "description": "Message received a reaction (v2.90+)", "category": "messages" },
{ "type": "message.revoked", "description": "Message was revoked/deleted (v2.90+)", "category": "messages" },
// Session Events (7 types)
{ "type": "session.ready", "description": "WhatsApp session is ready to send/receive messages", "category": "session" },
{ "type": "session.disconnected", "description": "WhatsApp session disconnected", "category": "session" },
{ "type": "session.qr_updated", "description": "QR code updated (scan required)", "category": "session" },
{ "type": "session.auth_failure", "description": "Authentication failed", "category": "session" },
{ "type": "session.zombie_detected", "description": "Zombie session detected - auto-recovery initiated (v3.8.24+)", "category": "session" },
{ "type": "session.recovered", "description": "Zombie session recovered successfully (v3.8.24+)", "category": "session" },
{ "type": "session.recovery_failed", "description": "Zombie session recovery failed (v3.8.24+)", "category": "session" },
// Group Events (3 types)
{ "type": "group.participant_added", "description": "Participant added to group", "category": "groups" },
{ "type": "group.participant_removed", "description": "Participant removed from group", "category": "groups" },
{ "type": "group.info_updated", "description": "Group info was updated", "category": "groups" },
// Contact Events (1 type)
{ "type": "contact.status_updated", "description": "Contact status text was updated", "category": "contacts" },
// Call Events (2 types)
{ "type": "call.received", "description": "Incoming call received", "category": "calls" },
{ "type": "call.ended", "description": "Call ended", "category": "calls" },
// Poll Events (1 type)
{ "type": "poll.vote_update", "description": "Poll vote was updated (v2.90+)", "category": "polls" }
]
}
POST /api/v1/webhooks/test
Send a test webhook event to verify your endpoint configuration.
// Request
const response = await fetch('https://apiwts.top/api/v1/webhooks/test', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://your-app.com/webhooks/whatsapp',
secret: 'your-webhook-secret' // Optional
})
});
// Response 200
{
"success": true,
"message": "Test webhook delivered successfully",
"testEventId": "test_1642598523000_abc123",
"deliveryStatus": "delivered"
}
GET /api/v1/webhooks/analytics/trends
⨠NEW in v2.12
Get temporal trends (daily success/failure counts) and latency percentiles for webhook deliveries.
// Request with custom period
const response = await fetch('https://apiwts.top/api/v1/webhooks/analytics/trends?days=14', {
headers: { 'X-API-Key': 'your-api-key' }
});
// Response 200
{
"period": {
"start": "2025-01-01",
"end": "2025-01-15",
"days": 14
},
"trends": [
{
"date": "2025-01-15",
"success": 145,
"failed": 5,
"avgLatencyMs": 250.5
},
{
"date": "2025-01-14",
"success": 132,
"failed": 2,
"avgLatencyMs": 180.3
}
],
"latency": {
"p50": 180.5,
"p95": 450.2,
"p99": 890.7,
"samples": 2500
}
}
| Query Parameter | Type | Default | Description |
|---|---|---|---|
days |
number | 7 | Number of days to analyze (min: 1, max: 30) |
đ Webhook Analytics Dashboard
⨠NEW in v2.10
Get comprehensive analytics and metrics about your webhook delivery performance, including success rates, latency, top failing events, and circuit breaker status.
GET /api/v1/webhooks/stats
Retrieve complete webhook delivery statistics and analytics.
Request Example
// JavaScript/Node.js
const response = await fetch('https://apiwts.top/api/v1/webhooks/stats', {
method: 'GET',
headers: {
'X-API-Key': 'your-api-key'
}
});
const stats = await response.json();
console.log('Webhook Analytics:', stats);
// cURL
curl -X GET https://apiwts.top/api/v1/webhooks/stats \
-H "X-API-Key: your-api-key"
Response Schema
{
"totalDeliveries": number, // Total webhook delivery attempts
"successRate": number, // Success percentage (0-100, 2 decimals)
"avgLatencyMs": number | null, // Average delivery latency in milliseconds
"failed": {
"total": number, // Total failed deliveries
"last24h": number // Failed deliveries in last 24 hours
},
"circuitBreakers": number, // Number of circuit breaker activations
"topFailingEvents": [ // Top 5 events with most failures
{
"event": string, // Event type
"failures": number, // Number of failures
"rate": number // Percentage of total failures (2 decimals)
}
]
}
Success Response (200 OK) - With Deliveries
{
"totalDeliveries": 15234,
"successRate": 97.52,
"avgLatencyMs": 245,
"failed": {
"total": 381,
"last24h": 12
},
"circuitBreakers": 3,
"topFailingEvents": [
{
"event": "message.received.media",
"failures": 145,
"rate": 38.06
},
{
"event": "session.qr_updated",
"failures": 89,
"rate": 23.36
},
{
"event": "message.sent",
"failures": 67,
"rate": 17.59
},
{
"event": "session.ready",
"failures": 45,
"rate": 11.81
},
{
"event": "message.delivered",
"failures": 35,
"rate": 9.19
}
]
}
Success Response (200 OK) - No Deliveries Yet
{
"totalDeliveries": 0,
"successRate": 0,
"avgLatencyMs": null,
"failed": {
"total": 0,
"last24h": 0
},
"circuitBreakers": 0,
"topFailingEvents": []
}
Error Response (404 Not Found)
{
"statusCode": 404,
"error": "Not Found",
"message": "No webhook configuration found for this tenant"
}
Metrics Explained
- totalDeliveries: COUNT(*) from webhook_deliveries table
- successRate: Calculated as (successCount / totalDeliveries) * 100, rounded to 2 decimals
- avgLatencyMs: Average time between created_at and delivered_at in milliseconds (only for completed deliveries)
- failed.total: Total count of deliveries with status = 'failed'
- failed.last24h: Failed deliveries created in the last 24 hours
- circuitBreakers: Number of webhooks disabled due to exceeding 10 consecutive failures
- topFailingEvents: Top 5 event types ordered by failure count, with percentage of total failures
Use Cases
- đ Monitor webhook health: Track success rate and latency trends
- đ Identify problematic events: Find which events are failing most frequently
- â ī¸ Circuit breaker alerts: Get notified when webhooks are auto-disabled
- đ Performance optimization: Use latency metrics to optimize webhook endpoints
- đ¨ Incident response: Check failed.last24h to detect recent delivery issues
Circuit Breaker Feature
The system automatically disables webhooks (active = false) when:
- đ¨ Threshold: 10 consecutive failures detected
- âąī¸ Check Frequency: Checked automatically every minute
- đ Manual Reset: Re-configure webhook to re-enable (contact administrator if needed)
- đ Tracking: circuitBreakers metric shows total disabled webhooks
Example: Monitoring Dashboard
// Fetch webhook stats every 30 seconds for dashboard
setInterval(async () => {
const response = await fetch('https://apiwts.top/api/v1/webhooks/stats', {
headers: { 'X-API-Key': process.env.API_KEY }
});
const stats = await response.json();
// Alert if success rate drops below 95%
if (stats.successRate < 95) {
console.error('â ī¸ Webhook success rate LOW:', stats.successRate + '%');
console.error('Top failing events:', stats.topFailingEvents);
}
// Alert if circuit breaker activated
if (stats.circuitBreakers > 0) {
console.error('đ¨ Circuit breaker activated! Webhooks disabled:', stats.circuitBreakers);
}
// Monitor latency
if (stats.avgLatencyMs > 1000) {
console.warn('âąī¸ High webhook latency:', stats.avgLatencyMs + 'ms');
}
}, 30000);
Future Enhancements (Roadmap)
- đ Temporal Graphs: 7-day success/failure trends with DATE_TRUNC
- đ Latency Percentiles: p50, p95, p99 using PERCENTILE_CONT
- đ Audit Log Integration: Full circuit breaker activation history
- đ Real-time Alerts: WebSocket notifications for critical events
POST /api/v1/webhooks/reset-circuit-breaker
⨠NEW in v2.11
Manually reactivate a webhook that was disabled by circuit breaker after 10 consecutive failures. Resets the failed_deliveries counter and sets active = true without changing webhook configuration.
Request Example
// JavaScript/Node.js
const response = await fetch('https://apiwts.top/api/v1/webhooks/reset-circuit-breaker', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key'
}
});
const result = await response.json();
console.log(result);
// cURL
curl -X POST https://apiwts.top/api/v1/webhooks/reset-circuit-breaker \
-H "X-API-Key: your-api-key"
Success Response (200 OK)
{
"success": true,
"message": "Circuit breaker reset successfully. Webhook is now active.",
"webhookConfigId": "webhook_config_abc123"
}
Error Response (404 Not Found)
{
"statusCode": 404,
"error": "Not Found",
"message": "No webhook configuration found for this tenant"
}
Error Response (409 Conflict)
{
"statusCode": 409,
"error": "Conflict",
"message": "Webhook is already active. Circuit breaker reset is only available for disabled webhooks."
}
When to Use
- đ§ After fixing webhook endpoint: Your server was down and circuit breaker triggered
- đ Debugging webhook issues: Iteratively test fixes without full reconfiguration
- ⥠Quick recovery: Preserves URL, events, secret - only resets counter
- đ Alternative to reconfigure: No need to provide all settings again
Comparison: Reset vs Reconfigure
| Feature | Reset Circuit Breaker (v2.11) | Reconfigure Webhook (v2.7) |
|---|---|---|
| Preserves URL | â Yes | â Must provide again |
| Preserves Events | â Yes | â Must provide again |
| Preserves Secret | â Yes | â Auto-regenerated |
| Resets Counter | â Yes (failed_deliveries = 0) | â Yes (new config) |
| Use Case | Quick recovery after fix | Change webhook settings |
đĨ Contact Management WWebJS Only
Manage WhatsApp contacts: list, retrieve, block, unblock, and get profile pictures.
GET /api/v1/contacts v3.8.26+
List all contacts from your WhatsApp session with optional pagination and filters.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string | No | Session ID (defaults to tenant's first session) |
| limit | number | No | Maximum contacts to return (default: 100, max: 500) (v3.8.26+) |
| isBlocked | boolean | No | Filter by blocked status: true = only blocked, false = only unblocked (v3.8.26+) |
| isMyContact | boolean | No | Filter by saved contacts: true = only saved in phone, false = not saved (v3.8.26+) |
Example Request (with filters)
// Get first 50 saved contacts that are not blocked
const response = await fetch('https://apiwts.top/api/v1/contacts?limit=50&isMyContact=true&isBlocked=false', {
method: 'GET',
headers: {
'X-API-Key': 'your-api-key'
}
});
Example Response (200 OK)
{
"success": true,
"contacts": [
{
"id": "5511999999999@c.us",
"name": "JoÃŖo Silva",
"number": "5511999999999",
"pushname": "JoÃŖo",
"isBlocked": false,
"isMyContact": true,
"profilePicUrl": "https://pps.whatsapp.net/v/..."
},
{
"id": "5511888888888@c.us",
"name": "Maria Santos",
"number": "5511888888888",
"pushname": "Maria",
"isBlocked": false,
"isMyContact": true,
"profilePicUrl": null
}
],
"total": 150,
"returned": 2,
"filters": {
"limit": 50,
"isBlocked": false,
"isMyContact": true
}
}
Response Fields (v3.8.26+)
| Field | Type | Description |
|---|---|---|
| contacts | array | List of contacts matching filters |
| contacts[].isMyContact | boolean | Whether contact is saved in phone's address book (NEW v3.8.26) |
| total | number | Total contacts before filtering |
| returned | number | Number of contacts returned after filters/limit (NEW v3.8.26) |
| filters | object | Applied filters summary (NEW v3.8.26) |
Errors
| Status | Error | Description |
|---|---|---|
| 404 | Not Found | No sessions found for this tenant |
| 400 | Bad Request | Session not READY or driver not WWebJS |
GET /api/v1/contacts/:phoneNumber
Get details of a specific contact by phone number.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| phoneNumber | string | Yes | Phone number with country code (e.g., 5511999999999, 41764791479, 14155552671) |
Example Request
const response = await fetch('https://apiwts.top/api/v1/contacts/5511999999999', {
method: 'GET',
headers: {
'X-API-Key': 'your-api-key'
}
});
Example Response (200 OK)
{
"success": true,
"contact": {
"id": "5511999999999@c.us",
"name": "JoÃŖo Silva",
"number": "5511999999999",
"pushname": "JoÃŖo",
"isBlocked": false,
"profilePicUrl": "https://pps.whatsapp.net/v/..."
}
}
Errors
| Status | Error | Description |
|---|---|---|
| 404 | Not Found | Contact not found in session |
| 400 | Bad Request | Invalid session state or driver |
POST /api/v1/contacts/:phoneNumber/block
Block a contact on WhatsApp. Useful for spam prevention.
Example Request
const response = await fetch('https://apiwts.top/api/v1/contacts/5511999999999/block', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key'
}
});
Example Response (200 OK)
{
"success": true,
"message": "Contact blocked successfully",
"phoneNumber": "5511999999999"
}
POST /api/v1/contacts/:phoneNumber/unblock
Unblock a previously blocked contact.
Example Request
const response = await fetch('https://apiwts.top/api/v1/contacts/5511999999999/unblock', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key'
}
});
Example Response (200 OK)
{
"success": true,
"message": "Contact unblocked successfully",
"phoneNumber": "5511999999999"
}
GET /api/v1/contacts/:phoneNumber/profile-pic
Get the profile picture URL of a contact. Returns null if contact has no profile picture or privacy settings prevent access.
Example Request
const response = await fetch('https://apiwts.top/api/v1/contacts/5511999999999/profile-pic', {
method: 'GET',
headers: {
'X-API-Key': 'your-api-key'
}
});
Example Response (200 OK)
{
"success": true,
"profilePicUrl": "https://pps.whatsapp.net/v/t61.24694-24/...",
"phoneNumber": "5511999999999",
"reason": null
}
Example Response (No Profile Picture)
{
"success": true,
"profilePicUrl": null,
"phoneNumber": "5511999999999",
"reason": null
}
Example Response (@lid Identifier - v3.8.31+)
{
"success": true,
"profilePicUrl": null,
"phoneNumber": "170321256693940@lid",
"reason": "LID_NOT_SUPPORTED"
}
- Privacy Settings: Contacts with private profile settings will return
profilePicUrl: null - Contact Sync: The contact must have recent interaction with the session for reliable retrieval
- Rate Limiting: WhatsApp may rate-limit excessive profile picture requests
- Cache Delay: Newly added contacts may take a few seconds to sync before their profile picture is available
- What is @lid? Linked Identity - WhatsApp's privacy feature to hide phone numbers in groups
- Profile Pictures: @lid contacts always return
profilePicUrl: null- this is by WhatsApp design, not a bug - Why? WhatsApp intentionally hides profile data for @lid identifiers to protect user privacy
- Workaround? None available - there is no method to convert @lid to phone number (by design)
- API Response: When @lid is detected, response includes
"reason": "LID_NOT_SUPPORTED"to identify this case - References: GitHub Issue #3519, Issue #3834
Use Cases
| Use Case | Endpoint | Description |
|---|---|---|
| CRM Integration | GET /contacts | Sync WhatsApp contacts with external CRM system |
| Spam Prevention | POST /contacts/:phone/block | Automatically block known spam numbers |
| Profile Backup | GET /contacts/:phone/profile-pic | Download and store contact profile pictures |
| Contact Validation | GET /contacts/:phone | Check if number exists in contacts before messaging |
- WWebJS Only: Contact Management is not available with Cloud API driver (Meta API limitation)
- Session State: All endpoints require session in READY state
- Rate Limiting: WhatsApp may rate-limit excessive profile picture requests
- Privacy: Some contacts may have privacy settings preventing profile picture access
đŦ Chat Management WWebJS Only
Manage WhatsApp chats with archive, mute, pin, and retrieval operations.
- List all chats with filtering (archived, unread, groups)
- Get individual chat details by ID
- Archive/unarchive chats for organization
- Mute/unmute chats with optional duration
- Pin/unpin important conversations
GET /api/v1/chats
Retrieve all chats from the WhatsApp session with optional filtering.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string | No | Session ID (defaults to tenant's first session) |
| archived | boolean | No | Filter by archived status |
| unreadOnly | boolean | No | Show only chats with unread messages |
| groups | boolean | No | Filter by group chats (true) or individual (false) |
| limit | number | No | Maximum chats to return (1-500, default 100) |
Example Request
const response = await fetch('https://apiwts.top/api/v1/chats?unreadOnly=true&limit=50', {
method: 'GET',
headers: {
'X-API-Key': 'your-api-key'
}
});
Example Response
{
"success": true,
"chats": [
{
"id": "5511999999999@c.us",
"name": "John Doe",
"isGroup": false,
"unreadCount": 3,
"lastMessage": {
"body": "Hey, are you there?",
"timestamp": "2025-10-03T14:30:00.000Z",
"fromMe": false
},
"timestamp": 1727967000000,
"archived": false,
"muted": false,
"muteExpiration": null,
"pinned": true
}
],
"total": 1
}
GET /api/v1/chats/:chatId
Get detailed information about a specific chat.
Example Request
const response = await fetch('https://apiwts.top/api/v1/chats/5511999999999@c.us', {
method: 'GET',
headers: {
'X-API-Key': 'your-api-key'
}
});
Example Response
{
"success": true,
"chat": {
"id": "5511999999999@c.us",
"name": "John Doe",
"isGroup": false,
"unreadCount": 3,
"lastMessage": {
"body": "Hey, are you there?",
"timestamp": "2025-10-03T14:30:00.000Z",
"fromMe": false
},
"timestamp": 1727967000000,
"archived": false,
"muted": false,
"muteExpiration": null,
"pinned": true
}
}
POST /api/v1/chats/:chatId/archive
Archive a chat to hide it from the main chat list.
Example Request
const response = await fetch('https://apiwts.top/api/v1/chats/5511999999999@c.us/archive', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001'
})
});
Example Response
{
"success": true,
"message": "Chat archived successfully",
"chatId": "5511999999999@c.us"
}
POST /api/v1/chats/:chatId/unarchive
Unarchive a chat to restore it to the main chat list.
Example Request
const response = await fetch('https://apiwts.top/api/v1/chats/5511999999999@c.us/unarchive', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001'
})
});
Example Response
{
"success": true,
"message": "Chat unarchived successfully",
"chatId": "5511999999999@c.us"
}
POST /api/v1/chats/:chatId/mute
Mute chat notifications. Duration is optional (indefinite if not provided).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | No | Session ID |
| duration | number | No | Mute duration in milliseconds |
Example Request (Mute for 24 hours)
const response = await fetch('https://apiwts.top/api/v1/chats/5511999999999@c.us/mute', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001',
duration: 86400000 // 24 hours in milliseconds
})
});
Example Response (Temporary Mute)
{
"success": true,
"message": "Chat muted until 2025-10-04T14:30:00.000Z",
"chatId": "5511999999999@c.us"
}
Example Request (Indefinite Mute)
const response = await fetch('https://apiwts.top/api/v1/chats/5511999999999@c.us/mute', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001'
})
});
Example Response (Indefinite Mute)
{
"success": true,
"message": "Chat muted indefinitely",
"chatId": "5511999999999@c.us"
}
POST /api/v1/chats/:chatId/unmute
Unmute a chat to restore notifications.
Example Request
const response = await fetch('https://apiwts.top/api/v1/chats/5511999999999@c.us/unmute', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001'
})
});
Example Response
{
"success": true,
"message": "Chat unmuted successfully",
"chatId": "5511999999999@c.us"
}
POST /api/v1/chats/:chatId/pin
Pin a chat to keep it at the top of the chat list.
Example Request
const response = await fetch('https://apiwts.top/api/v1/chats/5511999999999@c.us/pin', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001'
})
});
Example Response
{
"success": true,
"message": "Chat pinned successfully",
"chatId": "5511999999999@c.us"
}
POST /api/v1/chats/:chatId/unpin
Unpin a chat to remove it from the pinned section.
Example Request
const response = await fetch('https://apiwts.top/api/v1/chats/5511999999999@c.us/unpin', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001'
})
});
Example Response
{
"success": true,
"message": "Chat unpinned successfully",
"chatId": "5511999999999@c.us"
}
POST /api/v1/chats/:chatId/state
v2.85+ Set chat presence state - typing/recording indicators (WWebJS only).
Important: Only supported on wwebjs driver. Cloud API driver will return 422 error.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
sessionId |
string | Yes | Session identifier |
state |
string | Yes | State to set: typing, recording, or stop |
Example Request
// Simulate typing indicator
const response = await fetch('https://apiwts.top/api/v1/chats/5511999999999@c.us/state', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session-001',
state: 'typing'
})
});
// Response (200 OK - Success)
{
"success": true,
"message": "Chat state \"typing\" set successfully",
"data": {
"chatId": "5511999999999@c.us",
"state": "typing",
"appliedAt": "2025-11-23T08:00:00.000Z"
}
}
States
- typing: Shows "typing..." indicator to the recipient
- recording: Shows "recording..." indicator (for voice messages)
- stop: Clears any active presence state
Use Cases
- Human-like Bot Behavior: Simulate natural conversation by showing typing indicator before sending response
- Voice Message Indication: Show recording state before sending voice message
- Engagement: Increase user engagement by making automated responses feel more natural
Important Notes
- State is temporary and cleared automatically after ~20-30 seconds
- Recommended to send actual message within 5-10 seconds of setting state
- Only supported on WWebJS driver (Cloud API does not support presence simulation)
Use Cases
| Use Case | Endpoint | Description |
|---|---|---|
| Dashboard Inbox | GET /chats?unreadOnly=true | Display only chats with pending messages |
| Group Management | GET /chats?groups=true | List all group conversations |
| Notification Control | POST /chats/:id/mute | Silence noisy chats during business hours |
| Priority Chats | POST /chats/:id/pin | Keep important customers at the top |
| Archive Management | POST /chats/:id/archive | Hide completed support tickets |
- WWebJS Only: Chat Management is not available with Cloud API driver (Meta API limitation)
- Session State: All endpoints require session in READY state
- Chat ID Format: Use WhatsApp format (e.g., 5511999999999@c.us for individuals, xxxxx@g.us for groups)
- Client-Side Filtering: Filters are applied after fetching all chats (performance consideration for large chat lists)
- Mute Duration: Omit duration parameter for indefinite mute
đĨ Group Management
⨠NEW in v2.89
Manage WhatsApp groups with create, update, participant management, and administrative operations. Both WWebJS and Cloud API drivers supported.
- Create Groups: Both drivers (Cloud API limit: 8 participants, WWebJS: unlimited)
- Get Group Info: Retrieve group details, participants, and admins
- Add/Remove Participants: Manage group membership (batch operations supported)
- Update Group: Change group name and description
- Leave Group: Exit from a group programmatically
- Promote/Demote Admins: â ī¸ WWebJS driver only
- Invite Code Management: â ī¸ WWebJS driver only
- Group Settings: â ī¸ WWebJS driver only
- Cloud API - Participant Limit: Maximum 8 participants during group creation (WhatsApp Business API restriction)
- Cloud API - Admin Operations Not Supported: promote/demote, invite code retrieval/revoke, group settings
- WWebJS - No Limitations: Supports all group operations including admin management
- Error Handling: Unsupported operations return HTTP 400 with DriverNotSupportedError
POST /api/v1/groups
Create a new WhatsApp group with participants. Requires at least 1 participant. Returns group ID and participant count.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use for group creation |
| name | string | Yes | Group name (max 25 characters - WhatsApp limit) |
| description | string | No | Group description (max 512 characters - WhatsApp limit) |
| participants | string[] | Yes | Array of participant phone numbers with country code (Cloud API max 8, WWebJS unlimited) |
Example Request
curl -X POST https://apiwts.top/api/v1/groups \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "mysession",
"name": "Team Project Alpha",
"description": "Project discussion and updates",
"participants": [
"5511999999999",
"5511988888888",
"5511977777777"
]
}'
Example Response
{
"success": true,
"groupId": "123456789012345678@g.us",
"inviteCode": "AbCdEfGh1234567", // WWebJS only
"participantsAdded": 3
}
GET /api/v1/groups
WWebJS Only List all groups for a session. This is a convenience wrapper for GET /api/v1/chats with groups filter. Returns a simplified list of groups with basic information.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use |
| limit | number | No | Maximum number of groups to return (default: 100, max: 500) |
Example Request
curl -X GET "https://apiwts.top/api/v1/groups?sessionId=mysession&limit=50" \
-H "Authorization: Bearer YOUR_API_KEY"
Example Response
{
"groups": [
{
"id": "123456789012345678@g.us",
"name": "Team Project Alpha",
"participantCount": 8,
"isGroup": true
},
{
"id": "987654321098765432@g.us",
"name": "Customer Support",
"participantCount": 15,
"isGroup": true
}
],
"total": 2
}
Error Responses v2.92+
| Status | Error | Description |
|---|---|---|
| 400 | Bad Request | Session is not in READY state, or using Cloud API driver (not supported) |
| 401 | Unauthorized | Missing or invalid API key |
| 404 | Not Found | Session not found or driver not initialized |
| 503 | Service Unavailable | WhatsApp client not ready (try again later) |
Error Response Example
// 404 - Session not found
{
"statusCode": 404,
"error": "Not Found",
"message": "Session mysession not found"
}
// 400 - Session not ready
{
"statusCode": 400,
"error": "Bad Request",
"message": "Session mysession is not ready (current state: DISCONNECTED)"
}
// 503 - Client not initialized
{
"statusCode": 503,
"error": "Service Unavailable",
"message": "WhatsApp client is not ready. Please try again later."
}
GET /api/v1/groups/:groupId
Retrieve detailed information about a specific WhatsApp group including name, description, participants list, and admins.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID (format: 123456789-1234567890@g.us) |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use |
Example Request
curl -X GET "https://apiwts.top/api/v1/groups/123456789012345678@g.us?sessionId=mysession" \
-H "Authorization: Bearer YOUR_API_KEY"
Example Response
{
"id": "123456789012345678@g.us",
"name": "Team Project Alpha",
"description": "Project discussion and updates",
"participants": [
{
"id": "5511999999999@c.us",
"isAdmin": true,
"isSuperAdmin": true
},
{
"id": "5511988888888@c.us",
"isAdmin": false,
"isSuperAdmin": false
},
{
"id": "5511977777777@c.us",
"isAdmin": true,
"isSuperAdmin": false
}
],
"admins": [
"5511999999999@c.us",
"5511977777777@c.us"
],
"createdAt": "2025-01-15T10:30:00Z"
}
POST /api/v1/groups/:groupId/participants
Add one or more participants to a WhatsApp group. Returns results array showing success/failure for each participant.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID (format: 123456789-1234567890@g.us) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use |
| participants | string[] | Yes | Array of participant phone numbers to add |
Example Request
curl -X POST https://apiwts.top/api/v1/groups/123456789012345678@g.us/participants \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "mysession",
"participants": [
"5511966666666",
"5511955555555"
]
}'
Example Response
{
"success": true,
"results": [
{
"participant": "5511966666666@c.us",
"success": true
},
{
"participant": "5511955555555@c.us",
"success": false,
"error": "Participant has privacy settings restricting group invites"
}
]
}
DELETE /api/v1/groups/:groupId/participants
Remove one or more participants from a WhatsApp group. Requires admin permissions. Returns results array showing success/failure for each participant.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID (format: 123456789-1234567890@g.us) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use |
| participants | string[] | Yes | Array of participant phone numbers to remove |
Example Request
curl -X DELETE https://apiwts.top/api/v1/groups/123456789012345678@g.us/participants \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "mysession",
"participants": [
"5511966666666"
]
}'
Example Response
{
"success": true,
"results": [
{
"participant": "5511966666666@c.us",
"success": true
}
]
}
PATCH /api/v1/groups/:groupId
Update group name and/or description. Requires admin permissions. At least one field (name or description) must be provided.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID (format: 123456789-1234567890@g.us) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use |
| name | string | No | New group name (max 25 characters) |
| description | string | No | New group description (max 512 characters) |
Example Request
curl -X PATCH https://apiwts.top/api/v1/groups/123456789012345678@g.us \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "mysession",
"name": "Team Alpha - Q1 2025",
"description": "Updated project scope for Q1"
}'
Example Response
{
"success": true
}
POST /api/v1/groups/:groupId/leave
Leave a WhatsApp group. This action removes the session user from the group and cannot be undone programmatically.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID to leave (format: 123456789-1234567890@g.us) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use |
Example Request
curl -X POST https://apiwts.top/api/v1/groups/123456789012345678@g.us/leave \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "mysession"
}'
Example Response
{
"success": true
}
POST /api/v1/groups/:groupId/participants/promote
â ī¸ WWebJS Only Promote one or more participants to group admin. Requires admin permissions. Returns results array showing success/failure for each participant.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID (format: 123456789-1234567890@g.us) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use (WWebJS driver required) |
| participants | string[] | Yes | Array of participant phone numbers to promote |
Example Request
curl -X POST https://apiwts.top/api/v1/groups/123456789012345678@g.us/participants/promote \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "mysession",
"participants": [
"5511966666666"
]
}'
Example Response (WWebJS)
{
"success": true,
"results": [
{
"participant": "5511966666666@c.us",
"success": true
}
]
}
Example Response (Cloud API - Not Supported)
{
"statusCode": 400,
"error": "Driver Not Supported",
"message": "Feature \"promoteParticipants\" is not supported by Cloud API driver",
"code": "DRIVER_NOT_SUPPORTED",
"feature": "promoteParticipants",
"driver": "Cloud API"
}
POST /api/v1/groups/:groupId/participants/demote
â ī¸ WWebJS Only Demote one or more group admins to regular participants. Requires admin permissions. Returns results array showing success/failure for each participant.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID (format: 123456789-1234567890@g.us) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use (WWebJS driver required) |
| participants | string[] | Yes | Array of participant phone numbers to demote |
Example Request
curl -X POST https://apiwts.top/api/v1/groups/123456789012345678@g.us/participants/demote \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "mysession",
"participants": [
"5511977777777"
]
}'
Example Response
{
"success": true,
"results": [
{
"participant": "5511977777777@c.us",
"success": true
}
]
}
GET /api/v1/groups/:groupId/invite-code
â ī¸ WWebJS Only Retrieve the group's invite code (invitation link). Returns a 16-character code that can be used to join the group.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID (format: 123456789-1234567890@g.us) |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use (WWebJS driver required) |
Example Request
curl -X GET "https://apiwts.top/api/v1/groups/123456789012345678@g.us/invite-code?sessionId=mysession" \
-H "Authorization: Bearer YOUR_API_KEY"
Example Response
{
"inviteCode": "AbCdEfGh1234567",
"inviteLink": "https://chat.whatsapp.com/AbCdEfGh1234567"
}
POST /api/v1/groups/:groupId/invite-code/revoke
â ī¸ WWebJS Only Revoke the current invite code and generate a new one. Previous invite links will no longer work. Returns the new invite code.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID (format: 123456789-1234567890@g.us) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use (WWebJS driver required) |
Example Request
curl -X POST https://apiwts.top/api/v1/groups/123456789012345678@g.us/invite-code/revoke \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "mysession"
}'
Example Response
{
"success": true,
"newInviteCode": "XyZ9876543210Ab"
}
PATCH /api/v1/groups/:groupId/settings
â ī¸ WWebJS Only Update group settings for participant restrictions (who can send messages, edit group info).
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| groupId | string | Group ID (format: 123456789-1234567890@g.us) |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID to use (WWebJS driver required) |
| messageSend | boolean | No | If false, only admins can send messages |
| groupInfoEdit | boolean | No | If false, only admins can edit group info |
Example Request
curl -X PATCH https://apiwts.top/api/v1/groups/123456789012345678@g.us/settings \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "mysession",
"messageSend": false,
"groupInfoEdit": false
}'
Example Response
{
"success": true
}
- Group ID Format: Use WhatsApp format (e.g., 123456789-1234567890@g.us)
- Admin Permissions Required: Most operations (add/remove/promote/demote/settings) require the session user to be a group admin
- Cloud API Participant Limit: Maximum 8 participants during group creation (WhatsApp Business API restriction)
- WWebJS-Only Operations: promote/demote, invite code management, and group settings return HTTP 400 with DriverNotSupportedError on Cloud API
- Batch Operations: add/remove/promote/demote support multiple participants in a single request
- Character Limits: Group name (25 chars), description (512 chars) enforced by WhatsApp
- Leave Action Permanent: Leaving a group cannot be undone via API (requires manual re-invite)
đ¸ Instagram Messaging v3.10.0
Send and receive Instagram Direct Messages via Meta's Graph API. Available as a separate driver for Instagram Business accounts.
- Driver: Uses Meta Graph API (separate from WhatsApp Cloud API)
- Requirements: Instagram Business Account + Facebook Page + Meta App
- Authentication: OAuth 2.0 with long-lived tokens (60 days, auto-refresh)
- Webhooks: Receive DMs via webhook subscriptions
- 24-Hour Window: Must respond within 24 hours of user's last message
- Instagram Business Account: Personal accounts are NOT supported
- Connected Facebook Page: Page must be linked to Instagram account
- Meta App: Create app at developers.facebook.com
- Permissions:
instagram_basic,instagram_manage_messages,pages_manage_metadata,pages_messaging - App Review: Required for production use (Meta approval process)
Environment Configuration
Set these environment variables in your deployment:
# Meta App Configuration (Global - shared across all tenants)
META_APP_ID=your_meta_app_id
META_APP_SECRET=your_meta_app_secret
GRAPH_API_VERSION=v24.0
INSTAGRAM_WEBHOOK_VERIFY_TOKEN=your_secure_random_string
OAuth Flow (Admin Only)
Connect an Instagram Business account to your tenant:
Step 1: Start OAuth
GET /admin/api/instagram/oauth/start
// Response:
{
"authUrl": "https://www.facebook.com/v24.0/dialog/oauth?..."
}
Redirect admin user to authUrl to grant permissions.
Step 2: OAuth Callback
GET /admin/api/instagram/oauth/callback?code=xxx&state=yyy
// Response (on success):
{
"success": true,
"igUserId": "17841401234567890",
"igUsername": "mybusiness",
"pageId": "123456789012345",
"pageName": "My Business Page"
}
Sending Messages
Once connected, use the standard messaging endpoints with the Instagram session:
Send Text Message
POST /api/v1/messages/text
Content-Type: application/json
Authorization: Bearer your_api_key
{
"sessionId": "instagram-mybusiness",
"to": "17841409876543210", // Instagram-scoped ID (IGSID)
"message": "Hello from Instagram!"
}
Send Media
POST /api/v1/messages/media
Content-Type: application/json
Authorization: Bearer your_api_key
{
"sessionId": "instagram-mybusiness",
"to": "17841409876543210",
"mediaUrl": "https://example.com/image.jpg",
"type": "image",
"caption": "Check out this product!"
}
Send Quick Replies
POST /api/v1/messages/interactive
Content-Type: application/json
Authorization: Bearer your_api_key
{
"sessionId": "instagram-mybusiness",
"to": "17841409876543210",
"type": "quick_replies",
"body": "How can we help you today?",
"quickReplies": [
{"id": "order_status", "title": "Order Status"},
{"id": "returns", "title": "Returns"},
{"id": "support", "title": "Talk to Support"}
]
}
Receiving Messages (Webhooks)
Configure webhook URL in your Meta App dashboard:
Webhook URL: https://your-domain.com/webhooks/instagram
Verify Token: (same as INSTAGRAM_WEBHOOK_VERIFY_TOKEN env var)
Webhook Payload (message.received)
{
"event": "message.received",
"timestamp": 1704067200000,
"sessionId": "instagram-mybusiness",
"tenantId": "your-tenant-id",
"data": {
"from": "17841409876543210",
"to": "17841401234567890",
"body": "Hi, I have a question about my order",
"type": "text",
"messageId": "aWdfZAG1faW...",
"metadata": {
"platform": "instagram",
"timestamp": "2024-01-01T12:00:00.000Z"
}
}
}
Instagram-Specific Limitations
- 24-Hour Messaging Window: Can only send messages within 24 hours of user's last message (standard messaging). Human Agent tag extends to 7 days.
- No Phone Numbers: Uses Instagram-Scoped IDs (IGSID) instead of phone numbers
- No Groups: Instagram DMs are 1:1 only (group chats not supported via API)
- No Templates: Template messages not available (use WhatsApp Cloud API for templates)
- Media Limits: Images max 8MB, Videos max 25MB
- Rate Limits: Subject to Meta Graph API rate limiting
Supported Message Types
| Type | Support | Notes |
|---|---|---|
| Text | â | Plain text messages |
| Image | â | JPEG, PNG (max 8MB) |
| Video | â | MP4 (max 25MB, 60 seconds) |
| Audio | â | Voice messages |
| Sticker | â | Static and animated |
| Quick Replies | â | Up to 13 buttons |
| Generic Template | â | Carousel with cards |
| Ice Breakers | â | Conversation starters (FAQ) |
Token Management
The Instagram driver automatically manages token lifecycle:
- User Token: Long-lived (60 days), stored encrypted in database
- Page Token: Derived from user token, never expires while user token valid
- Auto-Refresh: Tokens refreshed 10 days before expiration
- Encryption: AES-256-GCM encryption for stored tokens
The system automatically refreshes tokens before expiration. If refresh fails (e.g., user revoked permissions), the session will be marked as token_expired and admin will need to re-authenticate via OAuth flow.
Advanced Features v3.10.0+
Instagram-specific advanced features for enhanced messaging capabilities.
Sender Actions (Typing Indicators)
Show typing indicators or mark messages as seen:
POST /api/v1/instagram/sessions/:sessionId/typing
Content-Type: application/json
Authorization: Bearer your_api_key
{
"recipientId": "17841409876543210",
"action": "typing_on" // typing_on | typing_off | mark_seen
}
// Response:
{
"success": true,
"action": "typing_on",
"recipientId": "17841409876543210"
}
typing_on- Shows typing indicator (auto-clears after 20s or next message)typing_off- Hides typing indicator immediatelymark_seen- Marks conversation as read (blue checkmarks)
Message Reactions
React to messages with emoji or remove reactions:
// Add reaction
POST /api/v1/instagram/sessions/:sessionId/messages/:messageId/react
Content-Type: application/json
Authorization: Bearer your_api_key
{
"recipientId": "17841409876543210",
"reaction": "love" // love | haha | wow | sad | angry | like | or any emoji
}
// Response:
{
"success": true,
"messageId": "aWdfZAG1faW...",
"reaction": "love"
}
// Remove reaction
DELETE /api/v1/instagram/sessions/:sessionId/messages/:messageId/react
Content-Type: application/json
Authorization: Bearer your_api_key
{
"recipientId": "17841409876543210"
}
// Response:
{
"success": true,
"messageId": "aWdfZAG1faW...",
"reaction": null
}
Multi-Image Sending (BETA)
Send multiple images in a single message:
POST /api/v1/instagram/sessions/:sessionId/messages/images
Content-Type: application/json
Authorization: Bearer your_api_key
{
"recipientId": "17841409876543210",
"imageUrls": [
"https://example.com/image1.jpg",
"https://example.com/image2.jpg",
"https://example.com/image3.jpg"
]
}
// Response:
{
"success": true,
"messageId": "aWdfZAG1faW...",
"status": "sent",
"imagesCount": 3
}
- Maximum: 10 images per message
- Size Limit: Each image max 8MB
- Beta Feature: May not be available for all Instagram accounts
- Error Code: Error subcode
2534068means feature not enabled for your app
User Moderation
Block, unblock, or mark users as spam:
// Block user
POST /api/v1/instagram/sessions/:sessionId/users/:userId/block
Authorization: Bearer your_api_key
// Response:
{
"success": true,
"userId": "17841409876543210",
"action": "block_user"
}
// Unblock user
DELETE /api/v1/instagram/sessions/:sessionId/users/:userId/block
Authorization: Bearer your_api_key
// Response:
{
"success": true,
"userId": "17841409876543210",
"action": "unblock_user"
}
// Move to spam
POST /api/v1/instagram/sessions/:sessionId/users/:userId/spam
Authorization: Bearer your_api_key
// Response:
{
"success": true,
"userId": "17841409876543210",
"action": "move_to_spam"
}
Instagram Webhook Events v3.10.0+
New webhook events for Instagram messaging:
// Reaction received webhook
{
"event": "message.reaction",
"timestamp": 1704067200000,
"sessionId": "instagram-mybusiness",
"tenantId": "your-tenant-id",
"data": {
"messageId": "aWdfZAG1faW...",
"from": "17841409876543210",
"reaction": "â¤ī¸",
"timestamp": "2024-01-01T12:00:00.000Z"
}
}
// Message edited webhook
{
"event": "message.edit",
"timestamp": 1704067200000,
"sessionId": "instagram-mybusiness",
"tenantId": "your-tenant-id",
"data": {
"messageId": "aWdfZAG1faW...",
"from": "17841409876543210",
"newBody": "Updated message text",
"timestamp": "2024-01-01T12:00:00.000Z"
}
}
Feature Flags
Instagram v3.10.0 features can be enabled/disabled via Redis-based feature flags:
| Flag | Default | Description |
|---|---|---|
INSTAGRAM_SENDER_ACTIONS_ENABLED |
â | typing_on/typing_off/mark_seen |
INSTAGRAM_REACTIONS_ENABLED |
â | React/unreact to messages |
INSTAGRAM_REACTION_WEBHOOKS_ENABLED |
â | Dispatch reaction webhooks |
INSTAGRAM_REPLY_TO_ENABLED |
â | Reply-to message parameter |
INSTAGRAM_MULTI_IMAGE_ENABLED |
â | Multi-image sending (BETA) |
INSTAGRAM_MODERATION_ENABLED |
â | Block/unblock/spam users |
INSTAGRAM_MESSAGE_EDIT_WEBHOOKS_ENABLED |
â | Message edit webhooks |
đ Message Templates Cloud API Only
Send pre-approved WhatsApp template messages with variable substitution, rich media, and interactive buttons.
- Pre-Approval Required: Templates must be created and approved in Facebook Business Manager
- Cannot Create via API: Templates are managed in Business Manager UI, not via API
- Session State: Session must be READY with Cloud API driver configured
- Components: Header (text/media), Body (with variables), Footer, Buttons (quick reply/call-to-action)
- Variable Substitution: Replace {{1}}, {{2}} placeholders at send-time for personalization
- Localization: Support for 60+ languages with language code specification
- Use Cases: Transactional notifications, marketing campaigns, OTP/authentication, customer support
- Quality Ratings: Template performance affects delivery limits (GREEN/YELLOW/RED ratings)
How to Create Templates
- Access Business Manager: Go to Facebook Business Manager
- Navigate to Templates: WhatsApp Manager â Message Templates
- Create Template: Click "Create Template" and configure components:
- Name: Lowercase, underscores, no spaces (e.g., order_confirmation)
- Category: Marketing, Utility, or Authentication
- Language: Select primary language (can add more later)
- Header: Optional text, image, video, document, or location
- Body: Message text with {{1}}, {{2}} variables (up to 1024 characters)
- Footer: Optional footer text (up to 60 characters)
- Buttons: Up to 3 buttons (Call to Action or Quick Reply)
- Submit for Review: Meta reviews templates (24-48 hours approval time)
- Wait for Approval: Template status: PENDING â APPROVED/REJECTED
- Use Approved Template: Only APPROVED templates can be sent via API
POST /api/v1/messages/template
Send a pre-approved WhatsApp template message with variable substitution.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
sessionId |
string | Yes | Session identifier (must be Cloud API session in READY state) |
to |
string | Yes | Recipient WhatsApp number in E.164 format with country code (e.g., +5511999999999, +41764791479, +14155552671) |
templateName |
string | Yes | Template name from Business Manager (1-512 characters) |
languageCode |
string | No | Template language code (e.g., 'en', 'pt_BR', 'es'). Default: 'en' |
headerVariables |
array | No | Header component variables (for media headers). Array of {type, value} |
bodyVariables |
string[] | No | Body variables to replace {{1}}, {{2}} placeholders |
buttons |
array | No | Button component values. Array of {type: 'url'|'quick_reply', value} |
Example: Simple Text Template
const response = await fetch('https://apiwts.top/api/v1/messages/template', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'cloud-session-001',
to: '+5511999999999',
templateName: 'welcome_message',
languageCode: 'pt_BR',
bodyVariables: ['JoÃŖo Silva']
})
});
const result = await response.json();
console.log(result);
// {
// "id": "msg_uuid_12345",
// "sessionId": "cloud-session-001",
// "to": "+5511999999999",
// "status": "sent",
// "type": "template",
// "timestamp": "2025-10-03T12:00:00.000Z",
// "driverMessageId": "wamid.HBgLNTU1MTEOTk5OTk5OTk5..."
// }
Example: Order Confirmation Template
// Template in Business Manager:
// Name: order_confirmation
// Body: Hi {{1}}, your order #{{2}} has been confirmed for ${{3}}.
// Footer: Thank you for shopping with us!
// Button: View Order (URL)
const response = await fetch('https://apiwts.top/api/v1/messages/template', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'cloud-session-001',
to: '+5511999999999',
templateName: 'order_confirmation',
languageCode: 'en',
bodyVariables: ['JoÃŖo Silva', 'ORD-12345', '49.99'],
buttons: [
{ type: 'url', value: 'https://example.com/orders/12345' }
]
})
});
const result = await response.json();
Example: Template with Image Header
// Template with image header
const response = await fetch('https://apiwts.top/api/v1/messages/template', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'cloud-session-001',
to: '+5511999999999',
templateName: 'product_launch',
languageCode: 'en',
headerVariables: [
{
type: 'image',
value: 'https://example.com/images/product.jpg'
}
],
bodyVariables: ['New Product', '25% OFF'],
buttons: [
{ type: 'url', value: 'https://example.com/products/new' },
{ type: 'quick_reply', value: 'LEARN_MORE' }
]
})
});
Response Schema
| Field | Type | Description |
|---|---|---|
id |
string | Internal message ID (UUID) |
sessionId |
string | Session identifier used for sending |
to |
string | Recipient WhatsApp number |
status |
string | Message status: 'sent', 'queued', 'delivered', etc. |
type |
string | Message type: 'template' |
timestamp |
string | ISO 8601 timestamp of message creation |
driverMessageId |
string | WhatsApp message ID from Cloud API (e.g., wamid.HBgL...) |
Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | Bad Request | WWebJS driver attempted (Cloud API only feature) |
| 400 | Bad Request | Session not in READY state |
| 404 | Not Found | Session not found or doesn't belong to tenant |
| 500 | Internal Server Error | Template not approved, invalid parameters, or Cloud API error |
Common Template Errors
// Error: WWebJS driver
{
"statusCode": 400,
"error": "Bad Request",
"message": "Template messages are only available for Cloud API driver (WWebJS does not support templates)"
}
// Error: Session not ready
{
"statusCode": 400,
"error": "Bad Request",
"message": "Session cloud-session-001 is not ready (current state: INITIALIZING)"
}
// Error: Template not approved
{
"statusCode": 500,
"error": "Internal Server Error",
"message": "Failed to send template: Template not found or not approved"
}
// Error: Invalid language code
{
"statusCode": 500,
"error": "Internal Server Error",
"message": "Failed to send template: Language code not supported for template"
}
Template Best Practices
- Use Descriptive Names: e.g., order_confirmation, password_reset, welcome_message
- Keep Body Concise: Max 1024 characters, avoid overly long messages
- Test Before Launch: Send test messages to verify variables and formatting
- Follow WhatsApp Guidelines: Avoid spam, excessive marketing, or misleading content
- Monitor Quality Ratings: Poor user feedback affects delivery limits (GREEN â YELLOW â RED)
- Localize Templates: Create language-specific versions for better engagement
- Limit Variables: Use only necessary variables to reduce complexity
- Button Clarity: Use clear call-to-action text (e.g., "View Order" not "Click Here")
Template Categories
| Category | Use Case | Examples |
|---|---|---|
| Utility | Transactional notifications | Order confirmations, shipping updates, appointment reminders, account alerts |
| Authentication | Security and verification | OTP codes, password resets, 2FA verification, login alerts |
| Marketing | Promotional messages | Product launches, seasonal sales, discount codes, event invitations |
- US Phone Numbers: Marketing templates to US numbers paused starting April 1, 2025
- Rate Limits: Per-user marketing message limits based on account tier and quality rating
- Opt-Out: Users can block or report marketing messages, affecting quality rating
- Compliance: Must comply with WhatsApp Business Policy and local regulations (GDPR, CAN-SPAM, etc.)
Driver Compatibility
| Feature | WWebJS | Cloud API |
|---|---|---|
| Template Messages | â Not Supported | â Full Support |
| Dynamic Templates | â Not Supported | â Pre-approval Required |
| Variable Substitution | â Not Supported | â Full Support |
| Interactive Buttons | â Deprecated | â Full Support |
| Media Headers | â Not Supported | â Image/Video/Document |
Related Resources
đ¤ MCP Reference (Model Context Protocol)
The WhatsApp Multi-Driver API exposes all its functionality via Model Context Protocol (MCP), enabling AI agents like Claude Code to interact with WhatsApp using natural language.
What is MCP?
MCP is an open protocol by Anthropic that connects AI applications to external tools and data sources in a standardized way.
How It Works
Claude Code
â JSON-RPC 2.0
MCP Server (https://apiwts.top/mcp)
â Internal API calls
WhatsApp Multi-Driver API
â Drivers
WhatsApp (WWebJS or Cloud API)
MCP Endpoint
POST /mcp
JSON-RPC 2.0 endpoint for AI tool integration.
// List available tools
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"result": {
"tools": [
{
"name": "sendTextMessage",
"description": "Send a text message via WhatsApp",
"inputSchema": {
"type": "object",
"properties": {
"sessionId": { "type": "string" },
"to": { "type": "string" },
"text": { "type": "string" }
},
"required": ["sessionId", "to", "text"]
}
}
// ... 57 tools
]
},
"id": 1
}
MCP Lifecycle Methods v3.7.6+
MCP defines lifecycle methods for protocol handshake and health checking. All methods require Bearer token authentication.
initialize
Handshake method to establish protocol version and capabilities. Required as the first method call.
// Request
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "initialize",
"id": 1,
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "my-client", "version": "1.0"}
}
}
// Response
{
"jsonrpc": "2.0",
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {
"name": "whatsapp-multi-driver-api",
"version": "3.7.6"
},
"instructions": "WhatsApp Multi-Driver API MCP Server. Use tools/list to discover available operations."
},
"id": 1
}
2024-11-05, 2025-03-26
notifications/initialized
Notification sent by client after receiving initialize response. No response expected (JSON-RPC notification).
// Request (notification - no id field)
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
// Response: HTTP 204 No Content (empty body)
ping
Simple health check method.
// Request
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "ping",
"id": 2
}
// Response
{
"jsonrpc": "2.0",
"result": {},
"id": 2
}
Authentication
MCP uses the same API key authentication as the REST API, but via Bearer token:
// Include API Key in Authorization header
Authorization: Bearer YOUR_API_KEY
46 Available Tools
Sessions Management (9 tools)
| Tool | Description |
|---|---|
createSession |
Create new WhatsApp session |
listSessions |
List all sessions |
getSession |
Get session details |
getSessionStatus |
Get status + QR availability |
getQRCode |
Get QR code (PNG base64) |
connectSession |
Initialize session |
restartSession |
Restart session |
deleteSession |
Delete session |
getSessionHealth |
Health metrics |
Messaging (20 tools)
| Tool | Description |
|---|---|
sendTextMessage |
Send text message |
sendMediaMessage |
Send media (image/video/document) |
sendTemplateMessage |
Send template (Cloud API only) |
sendLocationMessage |
Send location with coordinates (WWebJS + Cloud API) v3.4+ |
getMessage |
Get message status |
listMessages |
List messages |
revokeMessage |
Revoke/delete a sent message (WWebJS only) v2.84+ |
reactToMessage |
React to a message with emoji (WWebJS only) v2.85+ |
getMessageInfo |
Get detailed delivery info (ACK level, group read receipts) v2.85+ |
forwardMessage |
Forward message to another chat (WWebJS only) v2.85+ |
editMessage |
Edit a sent text message (~15 min window, WWebJS only) v3.4+ |
downloadMedia |
Download media (image/video/audio/doc) from message as base64 (WWebJS only) v3.4+ |
getQuotedMessage |
Get the original message that was quoted/replied to (WWebJS only) v3.4+ |
starMessage |
Star (favorite) a message (WWebJS only) v3.4+ |
unstarMessage |
Remove star (unfavorite) from a message (WWebJS only) v3.4+ |
getMessageReactions |
Get all emoji reactions on a message (WWebJS only) v3.4+ |
pinMessage |
Pin a message in chat for a specified duration (WWebJS only) v3.4+ |
unpinMessage |
Unpin a pinned message in chat (WWebJS only) v3.4+ |
getMessageMentions |
Get @mentions in a message - returns list of mentioned contacts (WWebJS only) v3.4+ |
getPollVotes |
Get votes from a poll message - returns list of voters with selected options (WWebJS only) v3.4+ |
Scheduled Messages (4 tools)
| Tool | Description |
|---|---|
scheduleMessage |
Schedule future message |
listScheduledMessages |
List scheduled messages |
cancelScheduledMessage |
Cancel pending message |
getScheduledMessageStats |
Get statistics |
Webhooks (6 tools)
| Tool | Description |
|---|---|
createWebhook |
Configure webhook URL |
listWebhooks |
List webhooks |
updateWebhook |
Update webhook |
deleteWebhook |
Delete webhook |
listWebhookDeliveries |
Delivery history |
testWebhook |
Test webhook |
Contacts (3 tools - WWebJS only)
| Tool | Description |
|---|---|
listContacts |
List contacts |
blockContact |
Block contact |
unblockContact |
Unblock contact |
Chats (6 tools - WWebJS only)
| Tool | Description |
|---|---|
listChats |
List conversations |
archiveChat |
Archive conversation |
unarchiveChat |
Unarchive conversation |
muteChat |
Mute conversation |
pinChat |
Pin conversation |
setChatState |
Set chat state - typing/recording indicators (WWebJS only) v2.85+ |
Groups (12 tools)
| Tool | Description | Driver |
|---|---|---|
createGroup |
Create new group | Both |
getGroupInfo |
Get group details | Both |
listGroups |
List all groups | WWebJS |
addGroupParticipants |
Add members to group | Both |
removeGroupParticipants |
Remove members from group | Both |
updateGroup |
Update group name/description | Both |
promoteGroupAdmin |
Promote member to admin | WWebJS |
demoteGroupAdmin |
Demote admin to member | WWebJS |
updateGroupSettings |
Update group settings | WWebJS |
getGroupInviteCode |
Get group invite link | WWebJS |
revokeGroupInvite |
Revoke invite link | WWebJS |
leaveGroup |
Leave a group | Both |
Monitoring (2 tools)
| Tool | Description |
|---|---|
getHealthStatus |
System health status |
getMetrics |
Prometheus metrics |
Usage Examples
Example 1: Tool Discovery
// Request: List all available tools
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"result": {
"tools": [...49 tools...]
},
"id": 1
}
Example 2: Call a Tool
// Request: Get health status
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "getHealthStatus",
"arguments": {}
},
"id": 2
}
// Response
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"status\":\"healthy\",\"database\":\"connected\",\"redis\":\"connected\"}"
}
]
},
"id": 2
}
Example 3: Send Message via Tool
// Request: Send WhatsApp message
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "sendTextMessage",
"arguments": {
"sessionId": "my-session",
"to": "+5511999999999",
"text": "Hello from MCP!"
}
},
"id": 3
}
// Response
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"id\":\"msg_123\",\"status\":\"queued\",\"sessionId\":\"my-session\"}"
}
]
},
"id": 3
}
Example 4: Complete Workflow
// Step 1: Create session
tools/call â createSession({ sessionId: "support" })
// Step 2: Connect session (generate QR)
tools/call â connectSession({ sessionId: "support" })
// Step 3: Get QR code
tools/call â getQRCode({ sessionId: "support" })
// Returns: { qr: "data:image/png;base64,..." }
// Step 4: Wait for session ready (webhook or polling)
// Step 5: Send message
tools/call â sendTextMessage({
sessionId: "support",
to: "+5511999999999",
text: "Hello from MCP Server!"
})
Example 5: Revoke Message (v2.84+)
// Request: Delete message for everyone
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "revokeMessage",
"arguments": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"revokeType": "everyone"
}
},
"id": 5
}
// Response (Success)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Message revoked successfully (everyone)\",\"revokedAt\":\"2025-11-23T06:00:00.000Z\",\"revokeType\":\"everyone\"}"
}
]
},
"id": 5
}
// Natural language usage with Claude Code
"Delete the message with ID 550e8400-e29b-41d4-a716-446655440000 for everyone"
"Revoke my last sent message"
"Delete message abc-123 only for me"
// Important notes
- Only supported on WWebJS driver (Cloud API will return error)
- Messages can be deleted for everyone within ~48 hours
- Requires message status: sent, delivered, or read
- revokeType: "me" (delete for yourself) or "everyone" (default)
Example 6: React to Message (v2.85+)
// Request: Add emoji reaction to a message
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "reactToMessage",
"arguments": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"emoji": "đ"
}
},
"id": 6
}
// Response (Success)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Reaction \\\"đ\\\" added successfully\",\"data\":{\"messageId\":\"550e8400-e29b-41d4-a716-446655440000\",\"emoji\":\"đ\",\"reactedAt\":\"2025-11-23T08:00:00.000Z\"}}"
}
]
},
"id": 6
}
// Natural language usage with Claude Code
"React to message 550e8400 with thumbs up emoji"
"Add heart reaction â¤ī¸ to the last message"
"React with đ to message abc-123"
// Important notes
- Only supported on WWebJS driver (Cloud API will return error)
- Common emojis: đ, â¤ī¸, đ, đŽ, đĸ, đ
- Message must have status: sent, delivered, or read
Example 7: Get Message Delivery Info (v2.85+)
// Request: Get detailed delivery information
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "getMessageInfo",
"arguments": {
"messageId": "550e8400-e29b-41d4-a716-446655440000"
}
},
"id": 7
}
// Response (Success - Individual Chat)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"data\":{\"id\":\"550e8400-e29b-41d4-a716-446655440000\",\"ack\":3,\"timestamp\":\"2025-11-23T08:00:00.000Z\"}}"
}
]
},
"id": 7
}
// Natural language usage with Claude Code
"Get delivery info for message 550e8400"
"Check if my last message was read"
"Show delivery details for message abc-123"
// ACK Levels
- -1: Error (message failed)
- 0: Pending (queued)
- 1: Server (sent to WhatsApp)
- 2: Device (delivered to recipient)
- 3: Read (read by recipient)
- 4: Played (voice/video played)
// Important notes
- Only supported on WWebJS driver
- Group chats include deliveredTo, readBy, playedBy arrays
Example 8: Forward Message (v2.85+)
// Request: Forward message to another chat
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "forwardMessage",
"arguments": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"targetChatId": "5511888888888@c.us"
}
},
"id": 8
}
// Response (Success)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Message forwarded successfully\",\"data\":{\"messageId\":\"550e8400-e29b-41d4-a716-446655440000\",\"targetChatId\":\"5511888888888@c.us\",\"forwardedAt\":\"2025-11-23T08:00:00.000Z\"}}"
}
]
},
"id": 8
}
// Natural language usage with Claude Code
"Forward message 550e8400 to +5511888888888"
"Forward the last message to chat 5511999999999@c.us"
"Forward message abc-123 to group 120363..."
// Important notes
- Only supported on WWebJS driver (Cloud API will return error)
- Message must have status: sent, delivered, or read
- newDriverMessageId may not be available (WWebJS limitation)
Example 9: Edit Message (v3.4+)
// Request: Edit a sent text message
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "editMessage",
"arguments": {
"messageId": "550e8400-e29b-41d4-a716-446655440000",
"newContent": "Updated message content"
}
},
"id": 9
}
// Response (Success)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Message edited successfully\",\"data\":{\"messageId\":\"550e8400-e29b-41d4-a716-446655440000\",\"newContent\":\"Updated message content\",\"editedAt\":\"2025-11-28T10:00:00.000Z\"}}"
}
]
},
"id": 9
}
// Natural language usage with Claude Code
"Edit message 550e8400 to say 'Updated content'"
"Change my last message to 'Fixed typo here'"
"Update the message I sent to 'Correct information'"
// Important notes
- Only supported on WWebJS driver (Cloud API will return error)
- Only text messages can be edited (not media/location/contacts)
- Messages can only be edited within ~15 minutes of sending
- Message must have status: sent, delivered, or read
Example 10: Download Media (v3.4+)
// Request: Download media from a message
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "downloadMedia",
"arguments": {
"messageId": "550e8400-e29b-41d4-a716-446655440000"
}
},
"id": 10
}
// Response (Success)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Media downloaded successfully\",\"data\":{\"messageId\":\"550e8400-e29b-41d4-a716-446655440000\",\"base64\":\"/9j/4AAQSkZJRgABAQAAAQABAAD/...\",\"mimetype\":\"image/jpeg\",\"filename\":\"photo.jpg\",\"filesize\":245678}}"
}
]
},
"id": 10
}
// Natural language usage with Claude Code
"Download the media from that message"
"Get the image from message 550e8400"
"Save the photo that was sent to me"
// Important notes
- Only supported on WWebJS driver (Cloud API provides URLs in webhooks)
- Message must contain media (image, video, audio, document)
- Returns base64-encoded data with mimetype and optional filename/filesize
Example 11: Send Location (v3.4+)
// Request: Send a location message
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "sendLocationMessage",
"arguments": {
"sessionId": "my-session-001",
"to": "5511999999999",
"latitude": -23.5505199,
"longitude": -46.6333094,
"description": "SÃŖo Paulo, Brazil",
"address": "Av. Paulista, 1578 - Bela Vista"
}
},
"id": 11
}
// Response (Success)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Location message sent successfully\",\"data\":{\"id\":\"550e8400-e29b-41d4-a716-446655440000\",\"driverMessageId\":\"true_5511999999999@c.us_ABCDEF123456\",\"sessionId\":\"my-session-001\",\"to\":\"5511999999999\",\"latitude\":-23.5505199,\"longitude\":-46.6333094,\"description\":\"SÃŖo Paulo, Brazil\",\"address\":\"Av. Paulista, 1578 - Bela Vista\",\"status\":\"sent\",\"timestamp\":\"2025-11-28T14:00:00.000Z\"}}"
}
]
},
"id": 11
}
// Natural language usage with Claude Code
"Send my location to contact 5511999999999"
"Share the coordinates -23.55, -46.63 with that user"
"Send the restaurant location to the group"
// Important notes
- Supported on WWebJS and Cloud API drivers
- Latitude must be between -90 and 90
- Longitude must be between -180 and 180
- Description and address are optional (max 256 chars each)
Example 12: Get Quoted Message (v3.4+)
// Request: Get the quoted/replied-to message
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "getQuotedMessage",
"arguments": {
"messageId": "msg-uuid-of-reply-message"
}
},
"id": 12
}
// Response (Success - message is a reply)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Quoted message retrieved successfully\",\"data\":{\"id\":\"true_5511999999999@c.us_AAABBCCCDDD\",\"from\":\"5511888888888@c.us\",\"to\":\"5511999999999@c.us\",\"body\":\"Original message text here\",\"type\":\"chat\",\"timestamp\":\"2025-11-28T10:30:00.000Z\",\"hasMedia\":false}}"
}
]
},
"id": 12
}
// Response (Success - message is NOT a reply)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Message is not a reply\",\"data\":null}"
}
]
},
"id": 12
}
// Natural language usage with Claude Code
"Get the original message that was replied to in message xyz"
"What message was this replying to?"
"Show me the quoted message context"
// Important notes
- WWebJS driver only
- Returns null if message is not a reply
- Useful for building conversation threads
Example 13: Star/Unstar Message (v3.4+)
// Request: Star (favorite) a message
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "starMessage",
"arguments": {
"messageId": "msg-uuid-here"
}
},
"id": 13
}
// Response (Success)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Message starred successfully\",\"data\":{\"messageId\":\"msg-uuid-here\",\"starredAt\":\"2025-11-29T10:30:00.000Z\"}}"
}
]
},
"id": 13
}
// Request: Unstar a message
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "unstarMessage",
"arguments": {
"messageId": "msg-uuid-here"
}
},
"id": 14
}
// Natural language usage with Claude Code
"Star this important message for later"
"Favorite message xyz"
"Remove the star from this message"
"Unstar message xyz"
// Important notes
- WWebJS driver only
- Starred messages appear in chat's "Starred Messages" section
- Useful for bookmarking important messages
Example 14: Set Chat State (v2.85+)
// Request: Show typing indicator
POST https://apiwts.top/mcp
Authorization: Bearer YOUR_API_KEY
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "setChatState",
"arguments": {
"sessionId": "my-session-001",
"chatId": "5511999999999@c.us",
"state": "typing"
}
},
"id": 9
}
// Response (Success)
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "{\"success\":true,\"message\":\"Chat state \\\"typing\\\" set successfully\",\"data\":{\"chatId\":\"5511999999999@c.us\",\"state\":\"typing\",\"appliedAt\":\"2025-11-23T08:00:00.000Z\"}}"
}
]
},
"id": 9
}
// Natural language usage with Claude Code
"Show typing indicator to +5511999999999"
"Simulate recording state in chat 5511999999999@c.us"
"Stop typing indicator in the current chat"
// States
- typing: Shows "typing..." indicator
- recording: Shows "recording..." indicator (voice messages)
- stop: Clears any active state
// Important notes
- Only supported on WWebJS driver
- State clears automatically after ~20-30 seconds
- Best practice: Send actual message within 5-10 seconds
- Use case: Simulate human-like bot behavior
Integration with Claude Code
To use this MCP server with Claude Code CLI:
# Register MCP server
claude mcp add --transport http whatsapp-api https://apiwts.top/mcp \
--header "Authorization: Bearer YOUR_API_KEY"
# List available tools
claude mcp list whatsapp-api
# Use via natural language
claude ask "Use whatsapp-api to send message 'Hello!' to +5511999999999 via session 'test'"
Error Handling
MCP follows JSON-RPC 2.0 error format:
// Error response
{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Invalid Request",
"data": {
"details": "Missing required parameter: sessionId"
}
},
"id": 1
}
// Common error codes
-32700: Parse error
-32600: Invalid Request
-32601: Method not found
-32602: Invalid params
-32603: Internal error
Testing & Debug
MCP Inspector (Official Tool)
npx @modelcontextprotocol/inspector https://apiwts.top/mcp
Visual interface to test tools, see responses, and debug errors.
Manual Testing with cURL
# List tools
curl -X POST https://apiwts.top/mcp \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
# Call tool
curl -X POST https://apiwts.top/mcp \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"getHealthStatus","arguments":{}},"id":2}'
- Natural Language: Use WhatsApp API via plain English commands
- No Documentation Required: AI agent knows all endpoints and parameters
- Autocomplete: Tools and parameters are discoverable
- Standardized: Works with any MCP-compatible AI agent
Additional Resources
đ§ Troubleshooting & Common Errors
Error: Missing Required Parameters
JSON-RPC Error Code: -32602
- Typo in parameter name (e.g.,
scheduledForinstead ofscheduledAt) - Parameter not sent in request
- Parameter with value
null,undefined, or empty string - Using wrong parameter name (e.g.,
idinstead ofwebhookId)
Example Error Response
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "ValidationError"
},
"result": {
"content": [
{
"type": "text",
"text": "{
\"code\": -32602,
\"error\": \"ValidationError\",
\"message\": \"Missing required parameters for scheduleMessage: scheduledAt\",
\"data\": {
\"toolName\": \"scheduleMessage\",
\"received\": [\"sessionId\", \"to\", \"text\", \"scheduledFor\"],
\"expected\": [\"sessionId\", \"to\", \"text\", \"scheduledAt\"],
\"missing\": [\"scheduledAt\"],
\"hint\": \"Did you mean 'scheduledAt' instead of 'scheduledFor'?\"
}
}"
}
]
},
"id": 1
}
How to Fix
- Check the error message for the exact parameter name required
- Look at the
hintfield for typo suggestions - Compare
receivedvsexpectedparameters - Correct the parameter name in your request
Common Typos & Fixes
| â Wrong (Common Typo) | â Correct | Tool |
|---|---|---|
scheduledFor |
scheduledAt |
scheduleMessage |
id |
webhookId |
updateWebhook, deleteWebhook, testWebhook |
sesionId |
sessionId |
All session tools |
chatid |
chatId |
All chat tools |
contactid |
contactId |
blockContact, unblockContact |
Required Parameters Quick Reference
| Tool | Required Parameters |
|---|---|
sendTextMessage | sessionId, to, text |
sendMediaMessage | sessionId, to, mediaType, mediaUrl |
sendTemplateMessage | sessionId, to, templateName, language |
scheduleMessage | sessionId, to, text, scheduledAt |
getMessage | sessionId, messageId |
listMessages | sessionId |
createSession | sessionId |
getSession | sessionId |
deleteSession | sessionId |
getQRCode | sessionId |
getSessionStatus | sessionId |
connectSession | sessionId |
restartSession | sessionId |
getSessionHealth | sessionId |
createWebhook | url, events |
updateWebhook | webhookId |
deleteWebhook | webhookId |
listWebhookDeliveries | webhookId |
testWebhook | webhookId |
listContacts | sessionId |
blockContact | sessionId, contactId |
unblockContact | sessionId, contactId |
listChats | sessionId |
archiveChat | sessionId, chatId |
unarchiveChat | sessionId, chatId |
muteChat | sessionId, chatId |
pinChat | sessionId, chatId |
Validation Features (v2.62+)
- Typo Detection: Levenshtein distance algorithm suggests correct parameter names
- Clear Error Messages: Exact parameter names that are missing
- JSON-RPC 2.0 Compliance: Standard error code -32602 for invalid params
- Helpful Hints: Automatic suggestions for common typos (edit distance ⤠2)
hint field first - it often contains the exact fix you need!
⥠Advanced Features
This section covers advanced configuration and deployment patterns for the WhatsApp API.
Session Reliability
The API includes automatic session recovery features:
- Auto-Reconnection: Automatic retry on temporary disconnections (3 attempts, 10s interval)
- Orphan Recovery: Sessions restored automatically on container restart
- Detached Frame Detection: Automatic recovery from Puppeteer corruption errors
- Health Monitoring: Continuous health checks with automatic status updates
Blue-Green Deployment
For zero-downtime deployments, the API supports blue-green architecture:
- Dual Environments: Blue and Green containers with isolated session volumes
- Instant Rollback: Traffic switch in under 5 seconds via symlink
- Session Sync: Automatic session synchronization between environments
- Shared Infrastructure: PostgreSQL and Redis shared between environments
Rate Limiting
Quality-based adaptive rate limiting protects your account:
- GREEN Rating: Normal sending limits
- YELLOW Rating: Reduced limits, monitoring required
- RED Rating: Severely restricted, review account health
- Recipient Cooldown: 6-10 second delays between messages to same recipient
đ§ Roadmap
Planned features for future releases. These endpoints are documented for reference but are not currently available.
Planned Message Types
Send location messages with GPS coordinates
Send contact cards (vCard format)
Create interactive polls with multiple options
Planned Group Features
Update group profile picture
Remove group profile picture
đ ī¸ Error Handling
HTTP Status Codes
| Code | Meaning | Solution |
|---|---|---|
| 200 | Success | Request completed successfully |
| 401 | Unauthorized | Check API Key - verify in admin panel |
| 404 | Not Found | Session doesn't exist - create session first |
| 409 | Conflict | Session already authenticated |
| 429 | Rate Limited | Too many requests - wait 60 seconds |
| 500 | Server Error | Try again later - check status page |
Error Response Format
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid API Key",
"timestamp": "2025-01-15T10:35:00.000Z"
}
Universal Error Handler
const handleApiError = (response) => {
switch (response.status) {
case 401:
return 'Invalid API Key - Check credentials';
case 404:
return 'Session not found - Create session first';
case 409:
return 'Session already authenticated';
case 429:
return 'Rate limit exceeded - Wait 60 seconds';
case 500:
return 'Server error - Try again later';
default:
return `Unexpected error: ${response.status}`;
}
};
â ī¸ Common Pitfalls & Solutions
1. CORS Issues
â Solution: Use proxy configuration, never direct external URLs in browser
// â WRONG - Direct external URL
fetch('http://external-api.com/endpoint')
// â
CORRECT - Relative URL with proxy
fetch('/api/v1/sessions')
2. Phone Number Format
â Solution: Use international format with country code (E.164)
// â
CORRECT - International format with country code
const validPhoneNumbers = [
'+5511999999999', // Brazil
'+41764791479', // Switzerland
'+14155552671', // United States
'+447911123456', // United Kingdom
'+8613912345678', // China
'+81312345678', // Japan
'+491512345678', // Germany
];
// â WRONG - Missing country code
const invalidPhoneNumbers = [
'11999999999', // Ambiguous - which country?
'7641234567', // Could be Russia (+7) or Kazakhstan
];
// Phone number formatting helper
const formatPhoneNumber = (phone) => {
// Remove all non-digit characters
const cleaned = phone.replace(/\D/g, '');
// Add + prefix if not present
return phone.startsWith('+') ? phone : `+${cleaned}`;
};
// Usage examples
const phoneNumber1 = formatPhoneNumber('+5511999999999'); // +5511999999999
const phoneNumber2 = formatPhoneNumber('41764791479'); // +41764791479
3. API Key Management
â Solution: Use environment variables or secure storage
// â WRONG - Hardcoded API key
const API_KEY = 'sk_live_a1b2c3d4e5f6...'; // Never do this!
// â
CORRECT - Environment variable
const API_KEY = import.meta.env.VITE_WHATSAPP_API_KEY;
// â
CORRECT - User input with localStorage
const [apiKey, setApiKey] = useState(
localStorage.getItem('whatsapp_api_key') || ''
);
4. Orphan Sessions & QR Code Errors
â Solution: POST the session again - API will auto-recover and recreate the driver
Cause: This error occurs when:
- The application was restarted
- Session exists in database but driver is not loaded in memory
- These are called "orphan sessions"
How to Fix:
// Step 1: Try creating the session again
const response = await fetch('https://apiwts.top/api/v1/sessions', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'scheduler'
})
});
// If session exists but driver is missing:
// API will automatically detect and recover the orphan session
// Returns 201 Created (not 409 Conflict)
// Step 2: Get QR code (should work now)
const qrResponse = await fetch('https://apiwts.top/api/v1/sessions/scheduler/qr', {
headers: { 'X-API-Key': 'your-api-key' }
});
Alternative (Manual Recovery):
// If auto-recovery doesn't work:
// 1. Delete the session
await fetch('https://apiwts.top/api/v1/sessions/scheduler', {
method: 'DELETE',
headers: { 'X-API-Key': 'your-api-key' }
});
// 2. Create it again
await fetch('https://apiwts.top/api/v1/sessions', {
method: 'POST',
headers: { 'X-API-Key': 'your-api-key', 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId: 'scheduler' })
});
5. Message Sending Errors
â Solution: Ensure session is READY before sending messages
Common Causes:
- Session not authenticated - Session is in QR_PENDING, INITIALIZING, or other non-READY state
- Session disconnected - WhatsApp session was disconnected and needs re-authentication
- Invalid phone number - Phone number format is incorrect (must be international format)
Solution - Check Session Status Before Sending:
// Step 1: Check session status
const sessionResponse = await fetch('https://apiwts.top/api/v1/sessions/my-session', {
headers: { 'X-API-Key': 'your-api-key' }
});
const session = await sessionResponse.json();
// Step 2: Only send if session is READY
if (session.state === 'READY') {
// Send message
const response = await fetch('https://apiwts.top/api/v1/messages/text', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'my-session',
to: '+5511999999999',
text: 'Hello World!'
})
});
} else {
console.error(`Session not ready: ${session.state}`);
// Handle: show QR code, reconnect, etc.
}
Error Response Examples:
// Error 400 - Session not ready
{
"statusCode": 400,
"error": "Bad Request",
"message": "Session my-session is not ready (current state: QR_PENDING). Please ensure the session is authenticated before sending messages."
}
// Error 404 - Session not found
{
"statusCode": 404,
"error": "Not Found",
"message": "Session my-session not found"
}
6. Session Management
â Solution: Monitor session status, implement reconnection logic
// Monitor session status
const checkSessionStatus = async (sessionId) => {
const response = await fetch(`/api/v1/sessions/${sessionId}`, {
headers: { 'X-API-Key': apiKey }
});
const session = await response.json();
if (session.state === 'DISCONNECTED') {
console.log('Session disconnected - need to re-authenticate');
// Get new QR code
}
};
6.1 Auto-Reconnection (v2.72+)
đ¯ Benefit: Sessions recover automatically from network issues without manual intervention
How Auto-Reconnection Works
- Temporary Disconnections: Auto-reconnect enabled (3 attempts, 10s interval)
- Permanent Disconnections: Require manual reconnection via dashboard or API
- Session State: Only READY/AUTHENTICATED sessions attempt auto-reconnect
Disconnection Types
| Reason | Type | Behavior |
|---|---|---|
| CONNECTION_LOST | Temporary | Auto-reconnect (3 attempts) |
| TIMEOUT | Temporary | Auto-reconnect (3 attempts) |
| CONFLICT | Temporary | Auto-reconnect (3 attempts) |
| LOGOUT | Permanent | Manual reconnect required |
| NAVIGATION | Permanent | Manual reconnect required |
| UNPAIRED | Permanent | Manual reconnect required |
Frontend Auto-Reconnect (Dashboard)
When clicking "Gerar QR Code" after disconnection:
- Frontend detects QR code unavailable (404)
- Automatically calls reconnect endpoint
- Waits 5 seconds for session initialization
- Retrieves and displays new QR code
- No intermediate prompts - seamless UX
Manual Reconnection via API
/restart to disconnect and re-initialize a session.
This preserves the session record and triggers a new QR code generation.
// Restart a disconnected session (using /restart endpoint)
const restartSession = async (sessionId) => {
// Step 1: Restart the session (disconnects + re-initializes)
const response = await fetch(`/api/v1/sessions/${sessionId}/restart`, {
method: 'POST',
headers: { 'X-API-Key': apiKey }
});
if (response.ok) {
console.log('Session restarting...');
// Step 2: Wait for QR code generation (typically 3-5 seconds)
setTimeout(async () => {
const qrResponse = await fetch(`/api/v1/sessions/${sessionId}/qr`, {
headers: { 'X-API-Key': apiKey }
});
if (qrResponse.ok) {
const blob = await qrResponse.blob();
const qrUrl = URL.createObjectURL(blob);
// Display QR code for user to scan
console.log('QR code ready:', qrUrl);
}
}, 5000);
}
};
// Alternative: Use /connect to re-initialize without full restart
const connectSession = async (sessionId) => {
const response = await fetch(`/api/v1/sessions/${sessionId}/connect`, {
method: 'POST',
headers: { 'X-API-Key': apiKey }
});
// Returns 201 if connection initiated, 200 if already connected
};
6.2 Zombie Session Detection (v3.8.24+)
đ¯ Solution: Multi-signal detection with automatic recovery
What is a Zombie Session?
A zombie session is a WhatsApp Web session that:
- Appears healthy: Status shows READY, health check passes
- API responds success: Send message returns "sent" with valid driverMessageId
- Messages never arrive: No ACK events, no delivery confirmation
- Silent failure: No errors, no warnings - just silent message loss
Detection Signals (3 independent checks)
| Signal | Trigger Condition | What it Means |
|---|---|---|
| ACK Rate | < 20% ACKs in last 3 messages (5 min window) | Messages sent but not confirmed by WhatsApp |
| Heartbeat | 2+ consecutive health check failures | WWebJS client not responding properly |
| Error Rate | > 50% errors in last 60 seconds | High failure rate in message polling |
Zombie Confirmation: 2 or more warning flags = ZOMBIE CONFIRMED
Automatic Recovery Flow
- đ Detection: Multi-signal check confirms zombie state
- đ¤ Webhook:
session.zombie_detectedsent to your endpoint - đ Recovery: Automatic shutdown + reconnection (if AUTO_RECONNECT_ENABLED=true)
- â
Success:
session.recoveredwebhook OR - â Failure:
session.recovery_failedwebhook
Webhook Payloads
// session.zombie_detected
{
"event": "session.zombie_detected",
"sessionId": "sensyxboutique",
"data": {
"metrics": {
"warningFlags": ["ACK_RATE", "HEARTBEAT"],
"messagesSent": 5,
"acksReceived": 0,
"ackRate": 0,
"heartbeatFailures": 2,
"detectedAt": "2026-01-03T05:00:00.000Z"
},
"action": "auto_reconnect_initiated",
"timestamp": 1735880400000
}
}
// session.recovered
{
"event": "session.recovered",
"sessionId": "sensyxboutique",
"data": {
"recoveryTime": 1735880430000,
"timestamp": 1735880430000
}
}
// session.recovery_failed
{
"event": "session.recovery_failed",
"sessionId": "sensyxboutique",
"data": {
"error": "Failed to reconnect: timeout",
"timestamp": 1735880460000
}
}
Environment Configuration
# Enable/Disable zombie detection
ZOMBIE_DETECTION_ENABLED=true
ZOMBIE_DRY_RUN=false
# ACK Monitoring
ZOMBIE_MIN_MESSAGES_FOR_CHECK=3
ZOMBIE_ACK_RATE_THRESHOLD=0.2
ZOMBIE_ACK_WINDOW_MS=300000
# Heartbeat
ZOMBIE_HEARTBEAT_INTERVAL_MS=30000
ZOMBIE_MAX_HEARTBEAT_FAILURES=2
# Error Rate
ZOMBIE_ERROR_WINDOW_MS=60000
ZOMBIE_ERROR_RATE_THRESHOLD=0.5
# Recovery
AUTO_RECONNECT_ENABLED=true
ZOMBIE_WARNINGS_TO_CONFIRM=2
SLA Guarantees
| Metric | Target | Description |
|---|---|---|
| Detection Time | < 2 minutes | From first failed message to zombie confirmed |
| Recovery Time | < 30 seconds | From zombie detected to session reconnected |
| Webhook Notification | < 5 seconds | Webhook sent immediately after detection |
session.zombie_detected webhook to monitor zombie events in your logging/alerting system.
đĄ Complete Examples
React Component Example
import React, { useState } from 'react';
const WhatsAppIntegration = () => {
const [apiKey, setApiKey] = useState('');
const [sessionId, setSessionId] = useState('my-session');
const [qrCode, setQrCode] = useState(null);
const [loading, setLoading] = useState(false);
const testConnection = async () => {
setLoading(true);
try {
const response = await fetch('/health', {
headers: { 'X-API-Key': apiKey }
});
if (response.ok) {
alert('â
API Connected!');
} else {
alert('â Connection failed');
}
} catch (error) {
alert('â Network error');
}
setLoading(false);
};
const getQrCode = async () => {
setLoading(true);
try {
const response = await fetch(`/api/v1/sessions/${sessionId}/qr`, {
headers: { 'X-API-Key': apiKey }
});
if (response.ok) {
const blob = await response.blob();
setQrCode(URL.createObjectURL(blob));
}
} catch (error) {
alert('â Failed to get QR code');
}
setLoading(false);
};
const sendMessage = async () => {
try {
const response = await fetch('/api/v1/messages/text', {
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId,
to: '+5511999999999',
text: 'Hello from React!'
})
});
if (response.ok) {
alert('â
Message sent!');
}
} catch (error) {
alert('â Failed to send message');
}
};
return (
<div>
<h1>WhatsApp Integration</h1>
<input
type="password"
placeholder="API Key"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
<input
type="text"
placeholder="Session ID"
value={sessionId}
onChange={(e) => setSessionId(e.target.value)}
/>
<button onClick={testConnection} disabled={loading}>
Test Connection
</button>
<button onClick={getQrCode} disabled={loading}>
Get QR Code
</button>
<button onClick={sendMessage} disabled={loading}>
Send Test Message
</button>
{qrCode && (
<div>
<h3>Scan with WhatsApp</h3>
<img src={qrCode} alt="QR Code" />
</div>
)}
</div>
);
};
export default WhatsAppIntegration;
đ API Information
Access OpenAPI specifications and API metadata programmatically.
GET /api/v1/openapi.json
Get the complete OpenAPI 3.0 specification as JSON (machine-readable API documentation).
// Request (no authentication required)
const response = await fetch('https://apiwts.top/api/v1/openapi.json');
const spec = await response.json();
// Response 200 - OpenAPI Specification
{
"openapi": "3.0.0",
"info": {
"title": "WhatsApp Multi-Driver API",
"version": "2.52.0",
"description": "Enterprise-grade WhatsApp API with multi-driver support"
},
"servers": [
{
"url": "https://apiwts.top/api/v1",
"description": "Production server"
}
],
"paths": {
"/sessions": { ... },
"/messages/text": { ... }
},
"components": {
"schemas": { ... },
"securitySchemes": { ... }
}
}
GET /api/v1/openapi.yaml
Get the complete OpenAPI 3.0 specification as YAML (human-readable format).
// Request (no authentication required)
const response = await fetch('https://apiwts.top/api/v1/openapi.yaml');
const specYaml = await response.text();
// Response 200 - OpenAPI Specification (YAML)
openapi: 3.0.0
info:
title: WhatsApp Multi-Driver API
version: 2.52.0
description: Enterprise-grade WhatsApp API with multi-driver support
servers:
- url: https://apiwts.top/api/v1
description: Production server
paths:
/sessions:
post:
summary: Create a new WhatsApp session
...
GET /api/v1/info
Get API version, status, and capabilities (lightweight metadata endpoint).
// Request (no authentication required)
const response = await fetch('https://apiwts.top/api/v1/info');
// Response 200
{
"name": "WhatsApp Multi-Driver API",
"version": "2.52.0",
"description": "Enterprise-grade WhatsApp API with multi-driver support",
"environment": "production",
"documentation": {
"swagger": "https://apiwts.top/docs",
"openapi": "https://apiwts.top/api/v1/openapi.json"
},
"capabilities": {
"drivers": [
"wwebjs",
"cloud"
],
"authentication": [
"apiKey",
"jwt"
],
"features": [
"multi-tenant",
"rate-limiting",
"message-queue",
"webhooks",
"media-upload",
"qr-code",
"auto-reconnect"
]
},
"support": {
"contact": "support@apiwhatsapp.com",
"issues": "https://github.com/Moser007/apiwhatsapp/issues"
}
}