JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 12
  • Score
    100M100P100Q47413F
  • License MIT

TypeScript SDK for WhatsApp Multi-tenant API - Clean, type-safe, and easy to use

Package Exports

  • @catalisa/wpp-sdk
  • @catalisa/wpp-sdk/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@catalisa/wpp-sdk) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@wpp/sdk

TypeScript SDK for WhatsApp Multi-tenant API - Clean, type-safe, and easy to use.

Installation

npm install @wpp/sdk
# or
yarn add @wpp/sdk

Quick Start

Admin Client (JWT Authentication)

import { WhatsAppAPIClient } from '@wpp/sdk';

const admin = new WhatsAppAPIClient({
  baseURL: 'https://api.example.com'
});

// Login
const { access_token, user } = await admin.login('admin@example.com', 'password');
console.log('Logged in as:', user.email);

// Create tenant
const tenant = await admin.tenants.create({ name: 'Acme Corp' });

// Create API token for tenant
const token = await admin.apiTokens.create(tenant.id, {
  name: 'Production Token',
  isTest: false
});
console.log('API Key:', token.key); // Store this securely!

// Connect device for tenant
await admin.whatsappAdmin.connect(tenant.id);

// Get QR code
const { qr } = await admin.whatsappAdmin.getQR(tenant.id);
console.log('Scan this QR:', qr);

User Client (API Token Authentication)

import { WhatsAppAPIClient } from '@wpp/sdk';

const client = new WhatsAppAPIClient({
  baseURL: 'https://api.example.com',
  auth: {
    type: 'apiToken',
    token: 'wpp_live_abc123_xyz789'
  }
});

// Connect session
const { correlationId } = await client.whatsapp.connect({
  callbackUrl: 'https://myapp.com/webhook'
});

// Send message
await client.whatsapp.sendMessage({
  jid: '5511999999999@s.whatsapp.net',
  text: 'Hello from SDK!'
});

// Send media
await client.whatsapp.sendMedia({
  jid: '5511999999999@s.whatsapp.net',
  type: 'image',
  url: 'https://example.com/photo.jpg',
  caption: 'Check this out!'
});

// List groups
const groups = await client.whatsapp.listGroups('tenant-id');
console.log('Groups:', groups);

Features

✨ Resource-Based API

Clean, intuitive API organized by domain:

client.auth          // Authentication
client.users         // User management (admin)
client.tenants       // Tenant management (admin)
client.apiTokens     // API token management (admin)
client.whatsapp      // WhatsApp operations (API token auth)
client.whatsappAdmin // WhatsApp admin operations (JWT auth)
client.webhooks      // Webhook management
client.branding      // Branding customization
client.health        // Health check

🔒 Dual Authentication Support

JWT (Admin endpoints)

const client = new WhatsAppAPIClient({
  baseURL: 'https://api.example.com',
  auth: { type: 'jwt', token: 'your-jwt-token' }
});

// Or login to get token automatically
await client.login('admin@example.com', 'password');

API Token (WhatsApp endpoints)

const client = new WhatsAppAPIClient({
  baseURL: 'https://api.example.com',
  auth: { type: 'apiToken', token: 'wpp_live_xxx' }
});

📝 Full TypeScript Support

100% typed with IntelliSense support:

import { SendMessageRequest, Group, AdvancedWebhook } from '@wpp/sdk';

const message: SendMessageRequest = {
  jid: '5511999999999@s.whatsapp.net',
  text: 'Hello!',
  callbackUrl: 'https://myapp.com/callback'
};

⚠️ Comprehensive Error Handling

import { APIError, AuthError, ValidationError, NotFoundError } from '@wpp/sdk';

try {
  await client.whatsapp.send({ jid: 'invalid', text: 'test' });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation failed:', error.errors);
  } else if (error instanceof AuthError) {
    console.error('Auth failed, status:', error.statusCode);
  } else if (error instanceof APIError) {
    console.error('API error:', error.message);
  }
}

Testing

The SDK includes comprehensive test suites demonstrating all features:

# Run all tests
npm run test:real          # Complete integration tests
npm run test:messaging     # Messaging operations only
npm run test:groups        # Group operations only
npm run test:tenant        # Specific tenant tests
npm run test:admin-crud    # Admin CRUD (users, tenants)
npm run test:branding      # Branding configuration
npm run test:webhooks      # Basic webhooks

# Or run directly
npx ts-node -r tsconfig-paths/register examples/test-real.ts
npx ts-node -r tsconfig-paths/register examples/test-messaging.ts
npx ts-node -r tsconfig-paths/register examples/test-groups.ts
npx ts-node -r tsconfig-paths/register examples/test-admin-crud.ts
npx ts-node -r tsconfig-paths/register examples/test-branding.ts
npx ts-node -r tsconfig-paths/register examples/test-basic-webhooks.ts

Test Coverage:

  • ✅ Authentication (JWT + API Token)
  • Admin CRUD Operations (users, tenants) - NEW!
  • ✅ Admin Operations (API tokens)
  • ✅ Messaging (text messages)
  • ✅ Group Operations (create, update, lifecycle testing with polling)
  • ✅ Advanced Webhooks (admin CRUD, filtering, logs, stats)
  • Basic Webhooks (CRUD) - NEW!
  • ✅ Device Management (connect, disconnect, reset)
  • Branding Configuration (CRUD) - NEW!
  • ⏭️ Media Operations (deferred to next phase)

Test Results:

Test File Results Coverage
test-real.ts 19/20 passing (95%) Admin ops, webhooks, device mgmt
test-messaging.ts 7/10 passing (70%, 3 skipped) Text messages, callbacks
test-groups.ts 21/22 passing (95.5%) Complete group lifecycle
test-specific-tenant.ts 7/7 passing (100%) Device operations
test-admin-crud.ts 17/17 passing (100%) 🆕 User & Tenant CRUD
test-branding.ts 6/6 passing (100%) 🆕 Branding config
test-basic-webhooks.ts 8/8 passing (100%) 🆕 Basic webhook CRUD

Total Coverage:

  • Endpoints Tested: 70/108 (64.8%) ⬆️ from 53.7%
  • Phase 1 Complete: +12 endpoints tested
  • All new tests: 100% passing 🎉

Test Highlights:

  • ✅ Complete lifecycle testing with proper async operation polling
  • ✅ Automatic cleanup with try/finally blocks
  • ✅ Comprehensive CRUD coverage for admin resources
  • ✅ Multi-webhook testing
  • ✅ User-tenant integration testing

Improvements in test-groups.ts:

  • Added waitForOperation() helper to poll async operations until completion
  • Added checkDeviceReady() helper to verify device status before tests
  • Refactored both user and admin tests with proper polling and group ID extraction
  • Added try/finally cleanup blocks to ensure test groups are deleted
  • Complete lifecycle testing: create → update → verify → cleanup

Examples

Send Message with Callback

const { correlationId } = await client.whatsapp.sendMessage({
  jid: '5511999999999@s.whatsapp.net',
  text: 'Hello!',
  callbackUrl: 'https://myapp.com/callback',
  correlationId: 'my-custom-id-123'
});

// Later, check operation status
const operation = await client.whatsapp.getOperation(correlationId);
console.log('Status:', operation.status); // 'pending', 'completed', or 'failed'
console.log('Result:', operation.result);

Media Operations

Sending Media

Send images, videos, audio, and documents via URL or base64:

// Send image from URL
await client.whatsapp.sendMedia({
  jid: '5511999999999@s.whatsapp.net',
  type: 'image',
  url: 'https://example.com/photo.jpg',
  caption: 'Check this out!'
});

// Send image from base64
await client.whatsapp.sendMedia({
  jid: '5511999999999@s.whatsapp.net',
  type: 'image',
  base64: '/9j/4AAQSkZJRg...', // Base64 encoded image (without data URI prefix)
  mimetype: 'image/jpeg',
  caption: 'Photo uploaded from device'
});

// Send audio (voice message)
await client.whatsapp.sendMedia({
  jid: '5511999999999@s.whatsapp.net',
  type: 'audio',
  base64: 'T2dnUwAC...', // Base64 encoded audio
  mimetype: 'audio/ogg; codecs=opus'
});

// Send document
await client.whatsapp.sendMedia({
  jid: '5511999999999@s.whatsapp.net',
  type: 'document',
  url: 'https://example.com/contract.pdf',
  filename: 'contract.pdf',
  mimetype: 'application/pdf',
  caption: 'Please review and sign'
});

// Send video
await client.whatsapp.sendMedia({
  jid: '5511999999999@s.whatsapp.net',
  type: 'video',
  url: 'https://example.com/video.mp4',
  caption: 'Watch this!'
});

Receiving Media (via Webhooks)

Incoming media is automatically downloaded, decrypted, and included in webhook events:

// Webhook payload structure for messages.upsert with media
interface MessageUpsertEvent {
  event: 'messages.upsert';
  tenantId: string;
  type: 'notify';
  messages: Array<{
    key: {
      remoteJid: string;
      fromMe: boolean;
      id: string;
    };
    message: {
      imageMessage?: {
        url: string;          // WhatsApp CDN URL (encrypted, temporary)
        mimetype: string;     // e.g., 'image/jpeg'
        caption?: string;
        fileSha256: string;
        fileLength: string;
        mediaKey: string;     // Decryption key
      };
      audioMessage?: {
        url: string;
        mimetype: string;     // e.g., 'audio/ogg; codecs=opus'
        seconds: number;      // Duration in seconds
        ptt: boolean;         // True if voice note
        mediaKey: string;
      };
      videoMessage?: { /* similar structure */ };
      documentMessage?: {
        url: string;
        mimetype: string;
        fileName: string;
        mediaKey: string;
      };
    };
    // Automatically downloaded and decrypted media (when downloadMedia=true)
    downloadedMedia?: {
      localPath: string;      // e.g., '/tmp/wpp-media/tenant123/1234567890_abc123.jpg'
      base64: string;         // Base64 encoded content (ready to use)
      mimetype: string;       // e.g., 'image/jpeg'
      type: 'image' | 'video' | 'audio' | 'document' | 'sticker';
    };
  }>;
}

// Example webhook handler
app.post('/webhook', (req, res) => {
  const { event, messages } = req.body;

  if (event === 'messages.upsert') {
    for (const msg of messages) {
      // Check if message has downloaded media
      if (msg.downloadedMedia) {
        const { type, base64, mimetype, localPath } = msg.downloadedMedia;

        console.log(`Received ${type}: ${mimetype}`);
        console.log(`Local path: ${localPath}`);

        // Use base64 directly (e.g., display in frontend)
        const dataUri = `data:${mimetype};base64,${base64}`;

        // Or read from localPath (within Docker container)
        // Note: localPath is only accessible within the worker container
      }

      // Text content (caption for media, or regular text)
      const text = msg.message?.conversation ||
                   msg.message?.extendedTextMessage?.text ||
                   msg.message?.imageMessage?.caption ||
                   msg.message?.audioMessage?.caption;

      if (text) {
        console.log('Text:', text);
      }
    }
  }

  res.status(200).send('OK');
});

Media Download Configuration

Media download is enabled by default per tenant. To disable:

-- Disable automatic media download for a tenant
UPDATE "TenantSettings"
SET "downloadMedia" = false
WHERE "tenantId" = 'your-tenant-id';

Supported Media Types

Type Supported Formats Max Size (recommended)
image JPEG, PNG, GIF, WebP 5 MB
video MP4, 3GP 16 MB
audio OGG (Opus), MP3, M4A 16 MB
document PDF, DOC, XLS, etc. 100 MB
sticker WebP 100 KB

SendMediaRequest Type

interface SendMediaRequest {
  jid: string;                                      // Recipient JID
  type: 'image' | 'video' | 'audio' | 'document';  // Media type
  url?: string;                                     // URL to download media from
  base64?: string;                                  // Base64 encoded content
  caption?: string;                                 // Caption text
  mimetype?: string;                                // MIME type (auto-detected if URL provided)
  filename?: string;                                // Filename (for documents)
  callbackUrl?: string;                             // Webhook for operation result
  correlationId?: string;                           // Custom tracking ID
}

Group Management

// Create group
const { correlationId } = await client.whatsapp.createGroup({
  subject: 'My Group',
  participants: ['5511111111111@s.whatsapp.net', '5522222222222@s.whatsapp.net']
});

const operation = await client.whatsapp.getOperation(correlationId);
const groupId = operation.result.gid;

// Update group subject
await client.whatsapp.updateGroupSubject({
  gid: groupId,
  subject: 'New Group Name'
});

// Add participants
await client.whatsapp.addGroupParticipants({
  gid: groupId,
  participants: ['5533333333333@s.whatsapp.net']
});

// Promote to admin
await client.whatsapp.promoteGroupParticipants({
  gid: groupId,
  participants: ['5533333333333@s.whatsapp.net']
});

Advanced Webhooks

// Create advanced webhook with filtering
const webhook = await client.webhooks.create({
  name: 'Group Messages Only',
  urls: ['https://api.example.com/webhook'],
  eventTypes: ['messages.upsert'],
  filters: {
    remoteJid: '120363XXX@g.us', // Specific group
    fromMe: false,                // Only incoming messages
    textContains: 'urgent'        // Only messages containing 'urgent'
  },
  secret: 'my-webhook-secret',
  retryCount: 3,
  timeoutMs: 10000
});

// Get webhook statistics
const stats = await client.webhooks.getStats(webhook.id);
console.log('Trigger count:', stats.triggerCount);
console.log('Success rate:', stats.successRate);
console.log('Avg response time:', stats.avgResponseTimeMs);

// Get delivery logs
const logs = await client.webhooks.getLogs(webhook.id, {
  success: false, // Only failed deliveries
  limit: 50
});

Admin Operations

// List all devices
const devices = await admin.whatsappAdmin.listDevices();

// Connect device for specific tenant
await admin.whatsappAdmin.connect('tenant-123');

// Send message as tenant
await admin.whatsappAdmin.sendMessage('tenant-123', {
  jid: '5511999999999@s.whatsapp.net',
  text: 'Admin sending message as tenant'
});

// Generate QR link for support
const qrLink = await admin.whatsappAdmin.generateQRLink('tenant-123', {
  expirationHours: 24
});
console.log('Share this link:', qrLink.url);

// Send QR link to user via WhatsApp
await admin.whatsappAdmin.sendQRLink('tenant-123', {
  recipientTenantId: 'tenant-456',
  recipientJid: '5511999999999@s.whatsapp.net'
});

Branding Customization

// Update branding for tenant
await admin.branding.update({
  tenantId: 'tenant-123',
  companyName: 'Acme Corp',
  primaryColor: '#FF5722',
  logoUrl: 'https://example.com/logo.png',
  headerMessage: 'Welcome to Acme Support',
  footerMessage: 'Powered by Acme Corp'
});

// Get public branding (no auth required)
const branding = await client.branding.get('tenant-123');

User Management

// Create user
const user = await admin.users.create({
  email: 'user@example.com',
  password: 'securepassword',
  role: 'user',
  tenantId: 'tenant-123'
});

// List all users
const users = await admin.users.list();

// Update user role
await admin.users.update(user.id, {
  role: 'admin'
});

// Delete user
await admin.users.delete(user.id);

Health Check

const health = await client.health.check();
console.log('API Status:', health.status); // 'ok'

Configuration

Advanced Configuration

const client = new WhatsAppAPIClient({
  baseURL: 'https://api.example.com',
  timeout: 60000, // 60 seconds (default: 30000)

  // Interceptors
  onRequest: async (config) => {
    console.log('Request:', config.method, config.url);
    return config;
  },

  onResponse: async (response) => {
    console.log('Response:', response.status);
    return response;
  },

  onError: (error) => {
    console.error('API Error:', error.message);
  }
});

Dynamic Auth Updates

const client = new WhatsAppAPIClient({
  baseURL: 'https://api.example.com'
});

// Login and set JWT
await client.login('admin@example.com', 'password');

// Switch to API token
client.setAuth('wpp_live_abc123', 'apiToken');

// Clear auth
client.clearAuth();

API Reference

Authentication

  • auth.login(email, password) - Login with credentials
  • auth.forgotPassword(email) - Request password reset
  • auth.resetPassword(token, newPassword) - Reset password
  • auth.me() - Get current user

Users (Admin)

  • users.list() - List all users
  • users.get(id) - Get user by ID
  • users.create(data) - Create user
  • users.update(id, data) - Update user
  • users.delete(id) - Delete user

Tenants (Admin)

  • tenants.list() - List all tenants
  • tenants.get(id) - Get tenant by ID
  • tenants.create(data) - Create tenant
  • tenants.update(id, data) - Update tenant
  • tenants.delete(id) - Delete tenant

API Tokens (Admin)

  • apiTokens.list(tenantId) - List tokens
  • apiTokens.create(tenantId, data) - Create token
  • apiTokens.delete(tenantId, tokenId) - Delete token

WhatsApp (User)

  • Session: connect(), reconnect(), reset(), logout(), ping()
  • Messaging: sendMessage(), sendReaction()
  • Media: sendMedia({ type, url?, base64?, caption?, mimetype?, filename? })
  • Groups: createGroup(), updateGroupSubject(), addGroupParticipants(), etc.
  • Queries: listGroups(), listChats(), listContacts(), getOperation()
  • Pairing: requestPairingCode(), getPairingCode()
  • Driver Config: configureDriver(), getDriverConfig()

WhatsApp Admin

  • Device: listDevices(), getDevice(), getQR()
  • Session: connect(), reconnect(), disconnect(), reset(), ping()
  • Operations: sendMessage(), getOperation()
  • Groups: listGroups(), createGroup(), manageGroupParticipants(), etc.

Webhooks

  • Basic: createBasic(), listBasic(), deleteBasic()
  • Advanced (Admin): createAdvanced(), listAdvanced(), updateAdvanced(), deleteAdvanced()
  • Advanced (User): create(), list(), update(), delete()
  • Logs & Stats: getLogs(), getStats()

Branding

  • get(tenantId) - Get branding (public)
  • update(data) - Update branding (admin)
  • delete(tenantId) - Delete branding (admin)

Health

  • check() - Health check

Error Types

  • APIError - Base API error
  • AuthError - Authentication/authorization errors (401, 403)
  • ValidationError - Validation errors (400, 422)
  • NotFoundError - Not found errors (404)
  • RateLimitError - Rate limit errors (429)
  • NetworkError - Network/connection errors

License

MIT

Support

For issues and questions, visit the GitHub repository.