Architecture Overview

Audience: developers deploying, customizing, or extending WaDesk. This page maps the moving parts — the Laravel app, the bundled Node.js bridge, and the database — so you know where each responsibility lives.

The Big Picture

WaDesk runs as two processes plus a database:

  • The Laravel 12 web app (PHP 8.2+) — the product itself: the UI, workspaces, plans and billing, the team inbox, campaigns, flows, and the admin panel. This is what users and admins log into.
  • The Node.js bridge (the node/ folder) — a long-running service that holds the live WhatsApp Web connections for the Unofficial API engine, fires scheduled and bulk sends on time, and forwards inbound messages back to the app.
  • MySQL 8.0+ (MariaDB 10.6+ works) — the single source of truth, and also the default store for the queue, cache, and sessions.

The two processes talk over plain HTTP, trusting each other through one shared secret token (no shared login). The app calls the bridge to send and schedule; the bridge calls the app to deliver inbound messages and status updates. See Node Bridge & Realtime.

                +-----------------------------------------------------------+
                |                        Browser                            |
                |          (Blade + Alpine.js + Tailwind, Vite)             |
                +-----------------------------+-----------------------------+
                                              | HTTPS (logged-in user)
                                              v
                +-----------------------------------------------------------+
                |                      Laravel 12 app                       |
                |                                                           |
                |   Page requests --> business logic --> send dispatchers   |
                |   Picks the right WhatsApp engine for each workspace      |
                |                                                           |
                |   Queue worker (database) for background sends            |
                +----+--------------------------------+---------------------+
                     |                                |
                     | HTTP  (shared token)           |  HTTPS (token / signature)
                     v                                ^
        +------------------------------+   +----------+---------------------+
        |   Node.js bridge             |   |   WhatsApp Cloud API (Meta)    |
        |   - live WhatsApp links      |   |   Twilio REST API              |
        |   - scheduler                |   +--------------------------------+
        |   - inbound -> app           |
        +--------------+---------------+
                       | reads/writes
                       v
        +------------------------------+
        |          MySQL 8.0+          |
        |  (also: queue, cache,        |
        |   sessions by default)       |
        +------------------------------+

Repository Layout

The project is a standard Laravel layout with the Node bridge living inside it under node/. The folders you will touch most often:

wadesk/
├── app/                  the PHP application code (business logic, send dispatchers, models)
├── config/               Laravel configuration
├── database/             migrations and seed data
├── lang/                 en.json + 20 translated locales
├── node/                 the bundled Node.js bridge (has its own dependencies)
├── resources/
│   ├── css/              stylesheets (dashboard + public landing pages)
│   ├── js/               front-end scripts
│   └── views/            page templates (user, admin, public)
├── routes/               URL definitions (app pages, admin panel, bridge/webhook endpoints)
├── public/               the web document root and the compiled assets
├── composer.json         PHP dependencies
├── package.json          front-end build (Vite + Tailwind v4)
└── vite.config.js        front-end build entry points
Note: the Node bridge installs its own dependencies inside node/, separate from the front-end build dependencies at the project root. They are two independent installs.

Technology Stack

LayerTechnology
Backend frameworkLaravel 12 on PHP 8.2+
Front-endBlade + Alpine.js + Tailwind CSS v4, compiled with Vite 7
DatabaseMySQL 8.0+ / MariaDB 10.6+
Queue / Cache / SessionDatabase by default (all configurable)
Unofficial engineNode.js + Express 5 + the Unofficial API WhatsApp Web library
Official enginesWhatsApp Cloud API (Meta, default API v23.0) and Twilio WhatsApp
SchedulingThe bridge fires time-based sends; the app runs its periodic jobs inline (see below)
Media transferSent inline over HTTP between the app and the bridge (no shared file storage needed)

Request & Send Lifecycle

A normal page view is a stock Laravel request. The interesting path is an outbound WhatsApp send, which always funnels through one of two send dispatchers (one for chat/campaigns/broadcasts/scheduled, one for the team inbox). Every send:

  1. Builds or loads the message record.
  2. Works out which WhatsApp engine the workspace uses.
  3. Checks the platform-wide emergency halt and the monthly message quota.
  4. Routes it: Unofficial API and Cloud API (WABA) sends both go to the Node bridge; Twilio is sent directly from PHP.
  5. Returns a uniform result so the caller knows what to record:
[ 'ok' => bool,           // did the provider accept it?
  'provider_id' => ?str,  // the message id, if sent
  'platform' => str,      // which engine handled it
  'local_only' => bool,   // saved but not actually sent out
  'error' => ?str ]       // failure reason, if any

Engine selection and the per-engine differences are covered on WhatsApp Engines.

One bridge entry for two engines. Cloud API (WABA) sends are also routed through the Node bridge, because the bridge already holds each number's WABA credentials and makes the Meta call itself. A PHP-only Meta path exists in the code but is intentionally not active — it is kept only as a documented failover reference.

Multi-Tenancy & Engine Resolution

Every tenant is a workspace. A user can belong to several workspaces and switch between them, and almost all data is scoped to one workspace.

"Which WhatsApp engine is this workspace on right now?" is decided in one place, in this order:

  1. The engine connected for that workspace (chosen when its channel was set up). A Click-to-WhatsApp ad connection is ignored here — it holds ad credentials, not a send engine.
  2. The platform default engine (an admin setting).
  3. A final fallback to the Unofficial API.

Always use this shared resolver wherever a form, stats card, or device picker should show only the active engine. Skipping it is how wrong-engine devices leak into pickers and cause silent send failures.

Core Concepts

A handful of concepts anchor the whole system. Knowing these makes the rest of the product easy to follow:

ConceptRole
WorkspaceThe tenant. Owns its plan, branding, and all of its data.
Channel connectionA workspace's chosen engine plus its credentials. Credentials are encrypted at rest, and the details stored differ per engine (Cloud API / Unofficial API / Twilio).
DeviceOne WhatsApp number connected to the Unofficial API by QR scan.
Messages & conversationsThe chat and campaign message history.
Inbox messagesThe team-inbox conversation bubbles (handled separately from chat/campaigns).
TemplatesWhatsApp message templates (body, buttons, carousel, approval status).
Scheduled / broadcast / campaignThe three bulk and automated send surfaces, each with its own recipient log.
Plans & featuresSubscription plans and the per-plan feature gating they grant.
Platform settingsAdmin-level configuration (default engine, branding footer, tokens, API version, and more).

Queue & Scheduling Model

WaDesk splits background work across the two processes:

  • The Laravel queue (database-backed by default) runs background jobs. You need a queue worker running — php artisan queue:work, or the bundled composer dev for local development.
  • The Node bridge owns time-based WhatsApp delivery: one-off scheduled sends, recurring schedules, broadcasts, and campaigns are registered with it and fired at the right moment in each recipient's timezone.
No system cron job is required. By design, the app's periodic tasks — inbox SLA escalation, snooze wake-ups, template status refresh — run automatically as a side effect of the screens that are already polling for updates (such as the team inbox). You do not need a schedule:run cron entry. Time-based sends are handled entirely by the Node bridge.

Security Boundaries

  • Logged-in pages — the app and admin panel sit behind login, 2FA, and role/permission checks.
  • Token-protected endpoints — the bridge and webhook endpoints run outside the browser login system. Bridge calls carry a shared secret header (compared safely), Twilio callbacks are verified by signature, and the browser extension uses bearer tokens. Keeping these off the login system is what lets the bridge's busy path run without competing for the browser session lock.
  • Encrypted secrets — engine credentials are encrypted at rest and only decrypted at the moment of a send (or when handing one number's settings to the bridge).
  • Emergency halt — an admin can freeze every outbound send platform-wide; both dispatchers check this before sending.

For the full security model see the Security section of these docs.

Where to Go Next

WaDesk Documentation