Waha

Advanced WhatsApp integration architecture and implementation guide for developers.

Waha Module Architecture Documentation

Overview

The Waha module is a comprehensive WhatsApp HTTP API integration built as a Laravel HMVC module. This documentation focuses on the architecture, class structure, and data flow to help developers understand the system design and implementation patterns.

Architecture Diagram

Waha Module Architecture

The architecture diagram illustrates the complete data flow through the Waha WhatsApp integration system, showing how external webhooks are processed through controllers, services, event handlers, and stored in the database.

Core Architecture Patterns

1. HMVC Module Structure

Modules/Waha/
├── app/
│   ├── Controllers/     # HTTP request handlers
│   ├── Models/          # Database entities
│   ├── Services/        # Business logic layer
│   ├── Events/          # Event handlers for webhooks
│   ├── DTOs/            # Data transfer objects
│   └── Resources/       # API response formatters
├── database/
│   └── migrations/      # Database schema
└── routes/
    └── api.php         # Route definitions

2. Data Flow Architecture

Webhook Processing Flow

External Waha Server → WebhookController → WebhookEventHandlerFactory 
                                        ↓
Event Handler (MessageHandler/CallHandler/etc.) → Service Layer → Models → Database

API Request Flow

Client Request → Controller → Service → Model → Database
                           ↓
                    Resource → JSON Response

Core Components

Models & Database Layer

WahaSession Model

class WahaSession extends Model
{
    // Represents WhatsApp account sessions
    // Relationships: hasMany(WahaMessage, WahaChat, WahaCall)
    // Key fields: name, status, config, user_id
}

WahaMessage Model

class WahaMessage extends Model
{
    // Stores individual WhatsApp messages
    // Relationships: belongsTo(WahaSession, WahaChat)
    // Key fields: message_id, type, content, media_url, status
}

WahaCall Model

class WahaCall extends Model
{
    // Logs call events and statistics
    // Relationships: belongsTo(WahaSession)
    // Key fields: call_id, from, to, type
}

Service Layer Design

Service Responsibilities

  • WahaSessionServices: Session lifecycle, status management
  • WahaMessageServices: Message processing, media handling
  • WahaChatServices: Chat organization, read status
  • WahaCallsServices: Call logging, statistics aggregation

Service Pattern Example

class WahaMessageServices
{
    public function processIncomingMessage(array $webhookData): WahaMessage
    {
        // 1. Validate webhook data
        // 2. Find or create session/chat
        // 3. Store message with metadata
        // 4. Handle media files if present
        // 5. Update read status
    }
}

Event Handler System

WebhookEventHandlerFactory Pattern

class WebhookEventHandlerFactory
{
    public static function getHandler(string $event): ?EventHandlerInterface
    {
        return match($event) {
            'message.any' => app(MessageHandler::class),
            'call.received' => app(CallHandler::class),
            'session.status' => app(SessionStatusHandler::class),
            default => null,
        };
    }
}

Handler Implementation

class MessageHandler implements EventHandlerInterface
{
    public function handle(array $data): void
    {
        // Extract message data
        // Process through service layer
        // Handle any business logic
        // Store in database
    }
}

Key Design Patterns

Repository Pattern Implementation

The module uses service classes as a repository pattern implementation:

// Service acts as repository for business logic
class WahaSessionServices
{
    public function findActiveSession(string $name): ?WahaSession
    {
        return WahaSession::where('name', $name)
                         ->where('status', 'working')
                         ->first();
    }

    public function createSession(array $data): WahaSession
    {
        // Business logic for session creation
        // Validation, external API calls, etc.
    }
}

Factory Pattern for Event Handlers

Dynamic event handler resolution using factory pattern:

class WebhookEventHandlerFactory
{
    private static array $handlers = [
        'message.any' => MessageHandler::class,
        'call.received' => CallHandler::class,
        'call.accepted' => CallHandler::class,
        'call.rejected' => CallHandler::class,
        'session.status' => SessionStatusHandler::class,
    ];

    public static function getHandler(string $event): ?EventHandlerInterface
    {
        if (!isset(self::$handlers[$event])) {
            return null;
        }

        return app(self::$handlers[$event]);
    }
}

DTO Pattern for Data Transfer

Structured data transfer between layers:

class CallDataDto
{
    public function __construct(
        public readonly string $sessionName,
        public readonly string $callId,
        public readonly string $from,
        public readonly string $to,
        public readonly string $type
    ) {}

    public static function fromWebhookData(array $data): self
    {
        return new self(
            sessionName: $data['session'],
            callId: $data['payload']['id'],
            from: $data['payload']['from'],
            to: $data['payload']['to'],
            type: $data['event']
        );
    }
}

Database Design Principles

Relationship Structure

-- Central session entity
waha_sessions (id, name, user_id, status, config)
    |
    ├── waha_messages (session_id, chat_id, message_id, type, content)
    ├── waha_chats (session_id, chat_id, name, last_message_at)
    ├── waha_calls (session_id, call_id, from, to, type)
    └── user_waha (user_id, session_id) -- Many-to-many pivot

Index Strategy

-- Performance indexes for common queries
INDEX idx_sessions_status ON waha_sessions(status)
INDEX idx_messages_session_chat ON waha_messages(session_id, chat_id)
INDEX idx_calls_session_type ON waha_calls(session_id, type)
INDEX idx_chats_session_updated ON waha_chats(session_id, updated_at)

Data Integrity Patterns

// Model relationships ensure referential integrity
class WahaMessage extends Model
{
    public function session(): BelongsTo
    {
        return $this->belongsTo(WahaSession::class);
    }

    public function chat(): BelongsTo
    {
        return $this->belongsTo(WahaChat::class);
    }
}

Security Architecture

HMAC Webhook Validation

Cryptographic validation of incoming webhooks:

class WebhookController extends Controller
{
    private function validateSignature(Request $request): bool
    {
        $signature = $request->header('X-Webhook-Signature');
        $payload = $request->getContent();
        $secret = config('waha.webhook_secret');

        $expectedSignature = hash_hmac('sha256', $payload, $secret);

        return hash_equals($expectedSignature, $signature);
    }

    public function handle(Request $request)
    {
        if (!$this->validateSignature($request)) {
            return response()->json(['error' => 'Invalid signature'], 401);
        }

        // Process webhook...
    }
}

Access Control Layer

User-session isolation through pivot table:

class WahaSessionServices
{
    public function getUserSessions(int $userId): Collection
    {
        return WahaSession::whereHas('users', function ($query) use ($userId) {
            $query->where('user_id', $userId);
        })->get();
    }

    public function canUserAccessSession(int $userId, int $sessionId): bool
    {
        return UserWaha::where('user_id', $userId)
                      ->where('session_id', $sessionId)
                      ->exists();
    }
}

Performance Considerations

Lazy Loading Strategy

Prevent N+1 queries with strategic eager loading:

class WahaMessageServices
{
    public function getSessionMessages(int $sessionId): Collection
    {
        return WahaMessage::with(['session', 'chat'])
                         ->where('session_id', $sessionId)
                         ->orderBy('created_at', 'desc')
                         ->paginate(50);
    }
}

Caching Layer

Cache frequently accessed data:

class WahaSessionServices
{
    public function getActiveSession(string $name): ?WahaSession
    {
        return Cache::remember("session.{$name}", 300, function () use ($name) {
            return WahaSession::where('name', $name)
                             ->where('status', 'working')
                             ->first();
        });
    }
}

Background Job Processing

Async processing for heavy operations:

class ProcessWebhookJob implements ShouldQueue
{
    public function handle(array $webhookData): void
    {
        $handler = WebhookEventHandlerFactory::getHandler($webhookData['event']);

        if ($handler) {
            $handler->handle($webhookData);
        }
    }
}

Extensibility Patterns

Event Handler Interface

Standardized interface for extending webhook handling:

interface EventHandlerInterface
{
    public function handle(array $data): void;
}

// Custom handler implementation
class CustomMessageHandler implements EventHandlerInterface
{
    public function handle(array $data): void
    {
        // Custom business logic
        // Integration with other systems
        // Custom validation/processing
    }
}

Service Provider Registration

Register custom handlers through service providers:

class WahaServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Register custom event handlers
        $this->app->bind(CustomEventHandler::class);

        // Override default handlers if needed
        $this->app->bind(MessageHandler::class, CustomMessageHandler::class);
    }
}

Configuration-Driven Behavior

Environment-based feature toggles:

class WahaMessageServices
{
    public function processMessage(array $data): void
    {
        // Core processing
        $message = $this->storeMessage($data);

        // Optional features based on config
        if (config('waha.enable_analytics')) {
            $this->recordAnalytics($message);
        }

        if (config('waha.enable_notifications')) {
            $this->sendNotification($message);
        }
    }
}

Testing Architecture

Unit Testing Strategy

Test individual components in isolation:

class WahaSessionServicesTest extends TestCase
{
    public function test_creates_session_with_valid_data()
    {
        $service = new WahaSessionServices();
        $data = ['name' => 'test-session', 'config' => []];

        $session = $service->createSession($data);

        $this->assertInstanceOf(WahaSession::class, $session);
        $this->assertEquals('test-session', $session->name);
    }
}

Integration Testing

Test component interactions:

class WebhookIntegrationTest extends TestCase
{
    public function test_processes_incoming_message_webhook()
    {
        // Arrange: Create session and webhook payload
        $session = WahaSession::factory()->create();
        $payload = ['event' => 'message.any', 'session' => $session->name];

        // Act: Send webhook request
        $response = $this->postJson('/api/waha/webhook', $payload);

        // Assert: Message created and response correct
        $response->assertOk();
        $this->assertDatabaseHas('waha_messages', ['session_id' => $session->id]);
    }
}

Monitoring & Observability

Logging Strategy

Structured logging for operations tracking:

class MessageHandler implements EventHandlerInterface
{
    public function handle(array $data): void
    {
        Log::info('Processing webhook message', [
            'event' => $data['event'],
            'session' => $data['session'],
            'message_id' => $data['payload']['id'] ?? null
        ]);

        try {
            // Process message...
            Log::info('Message processed successfully');
        } catch (\Exception $e) {
            Log::error('Failed to process message', [
                'error' => $e->getMessage(),
                'data' => $data
            ]);
            throw $e;
        }
    }
}

Metrics Collection

Track key performance indicators:

class WahaMetricsService
{
    public function recordMessageProcessed(string $sessionName, string $type): void
    {
        // Record metrics for monitoring
        Cache::increment("metrics.messages.{$sessionName}.{$type}");
        Cache::increment("metrics.messages.total");
    }

    public function getSessionStats(string $sessionName): array
    {
        return [
            'total_messages' => Cache::get("metrics.messages.{$sessionName}.total", 0),
            'text_messages' => Cache::get("metrics.messages.{$sessionName}.text", 0),
            'media_messages' => Cache::get("metrics.messages.{$sessionName}.media", 0),
        ];
    }
}

Deployment Considerations

Environment Configuration

Key configuration points for different environments:

// config/waha.php
return [
    'webhook_secret' => env('WAHA_WEBHOOK_SECRET'),
    'api_url' => env('WAHA_API_URL', 'http://localhost:3000'),
    'timeout' => env('WAHA_TIMEOUT', 30),
    'enable_analytics' => env('WAHA_ENABLE_ANALYTICS', false),
    'enable_caching' => env('WAHA_ENABLE_CACHING', true),
    'queue_webhooks' => env('WAHA_QUEUE_WEBHOOKS', true),
];

Queue Configuration

Async processing setup:

// config/queue.php - Add Waha-specific queue
'connections' => [
    'waha' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => 'waha-webhooks',
        'retry_after' => 90,
        'block_for' => null,
    ],
];

Database Optimization

Production database considerations:

-- Partitioning for large message tables
CREATE TABLE waha_messages_2024 PARTITION OF waha_messages
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');

-- Archival strategy for old data
CREATE TABLE waha_messages_archive AS 
SELECT * FROM waha_messages 
WHERE created_at < NOW() - INTERVAL '6 months';

API Integration

For complete API endpoints and interactive testing:

  • Swagger Documentation: /docs/admin#tag/Waha
  • Postman Collection: Export available from Swagger UI
  • Authentication: Bearer token required for all endpoints

Development Workflow

Module Structure Best Practices

  • Follow HMVC pattern consistently
  • Separate concerns: Controllers → Services → Models
  • Use DTOs for complex data transfer
  • Implement interfaces for extensibility
  • Write comprehensive tests

Code Standards

  • PSR-12 coding standards
  • Laravel naming conventions
  • PHPDoc documentation
  • Type declarations
  • Error handling with try-catch blocks

This architecture documentation provides the foundation for understanding and extending the Waha module's functionality within the larger HMVC application structure.