This guide summarizes how to explore, modify, and validate the WhatsApp HTTP API (WAHA) codebase when assisting as an automation or coding agent.
- When you asked to refactor or "apply new lib" instead of current one - do not change the behavior, make a minimum amount of changes possible. If you see some edge case during that process that haven't been covered - tell it, suggest fix, but don't change the code.
- WAHA ships in Core and Plus editions. Core lives under
src/coreand only supports the default session plus minimal media features. Plus extends core viasrc/plusto add multi-session orchestration, richer media handling, and external storage integrations. - Core code must remain free from Plus-only references. A pre-commit hook
rejects the word
plusinside core files; reuse abstractions exposed through@waha/core/**instead of importing Plus modules from Core. - Commit subjects are validated: changes that touch
src/plusrequire a[PLUS] …prefix and must not include non-Plus files; everything else must use[core] …. Verify against./.precommit/validate_commit_message.pyif unsure.
- Runtime: Node.js 22.x, Yarn 3.6 (Berry). Always install packages and run scripts with Yarn.
- Framework: NestJS v11 with dependency injection, modular controllers in
src/api, and Pino-based logging vianestjs-pino. - Engines: WhatsApp engines are abstracted (
WEBJS,GOWS,NOWEB). Core usesSessionManagerCore; Plus swaps toSessionManagerPluswith extra storage backends (Mongo/Postgres/SQLite). - ESM Bridge: ESM-only dependencies (Baileys) load through
src/vendor/esm.ts. Add new ESM modules there to ensure they load exactly once. - Utilities: RxJS streams (
SwitchObservable,DefaultMap) drive webhook event fan-out. Prefer existing helpers insrc/utilsandsrc/core/utilsbefore adding bespoke logic. - OS - the project works on any OS inside the Docker container; the base
image is defined in
Dockerfile. - CPU - the image must run on both
x86_64(including pre-v2 CPUs without SSE4.2) andaarch64.
src/main.ts: runtime entry point; dynamically loads the correct AppModule (Core vs Plus) and configures global interceptors/filters.src/api/**: REST controllers and WebSocket gateway. Keep handlers thin; delegate to managers/services. HTTP routes follow/api/{sessionName}/…; always thread the session name through request DTOs and guards instead of hardcodingdefault.src/core/**: shared abstractions (config services, engine bootstrap, storage, session management). Core only allows thedefaultsession and cleans up storage on boot.src/plus/**: multi-session orchestration, advanced media services, and external persistence layers (Mongo, Postgres). Reuse this layer when adding Plus-only capabilities.src/apps/**: integrations (e.g., ChatWoot) and application-specific services.src/structures/**andsrc/utils/**: DTOs, enums (event names followdomain.action), helper utilities. Maintain naming consistency when introducing new events.tests/**: Jest-based suites; do not add new tests unless explicitly asked, but keep existing tests working.
- Favor composable, long-lived solutions. If a helper already exists (e.g.,
parseBool,DefaultMap, media factories), extend it instead of reinventing logic. Lodash ships with the project—prefer its utilities over hand-rolled helpers. Import lodash as a namespace (import * as lodash from 'lodash';) so helpers likelodash.camelCasestay consistent across files. When a new third-party library seems necessary, confirm with the user, especially if the package looks stale. - Stick to NestJS patterns: inject dependencies through constructors, expose provider tokens from modules, and keep controllers free from business logic.
- Logging goes through injected
PinoLoggeror helpers insrc/utils/logging.ts.console.logis blocked by pre-commit. - Respect path aliases (
@waha/...) defined intsconfig.json; keep imports consistent (use absolute aliases, not relative../../../). - Prefer named function declarations over
constarrow functions when possible. - Avoid naming unused variables with a leading underscore; if a parameter is
required by a signature, explicitly
voidit instead. - Do not write verbose ternaries like
condition !== undefined ? condition : default; use idiomatic helpers such as??(nullish coalescing) or existing boolean parsers so flags stay readable. - Do not place
awaitor other async calls inside ternary expressions (?:); use explicitif/elseblocks instead. - For configs, prefer runtime configurability over constants. Environment keys
follow
WAHA_*for global values andWAHA_SESSION_CONFIG_*/session.config.*for per-session overrides. If both env and config are supported, honor both (WAHA_WEBJS_CONFIG_*vs.session.webjs.config.*).
- Keep identifiers (classes, methods, variables) and code comments in English so the codebase stays consistent for the global team.
- Route user-visible strings through the existing i18n structure rather than
hardcoding text. ChatWoot copy belongs in
src/apps/chatwoot/i18nwith English as the source locale before adding translations.
- Avoid introducing synthetic events; map onto existing webhook or engine events
whenever plausible. Always use the official ChatWoot API client located in
src/apps/chatwoot/client. - When adding events, align consumer names with webhook/event identifiers (e.g.,
message.any).
- Identify whether work targets Core, Plus, or shared layers. If Plus-only,
isolate changes to
src/plusand ensure the commit prefix matches. - Lean on existing services/managers; extend the appropriate session manager rather than branching logic inline.
- After edits run:
pre-commit run --all-filesyarn buildyarn test --watchman=false
- Do not start the application yourself; ask the user to run it if runtime validation is required.
- Capture any assumptions or open questions for the user, especially when touching configs, introducing dependencies, or modifying public APIs.
- Concurrency-sensitive sections (session start/stop) rely on
async-lock. When adding async flows, reuseSessionManager.withLockor existing retry utilities (promiseTimeout,waitUntil). - Media features centralize through
MediaManagerandMediaStorageFactory. When altering media behavior, update both Core and Plus variants where applicable. - Keep docs and code ASCII unless a file already uses other characters. When updating documentation, mirror the concise, actionable tone used here.
Always define key for objects (and in return too)
// NO
const variable = 123;
const b = { variable };
// YES
const variable = 123;
const b = { variable: variable };Use the three-line comment style for section headers inside files:
// NO
// ─── Section name ─────────────────────────────────────────────────────────────
// YES
//
// Section name
//You can find and read related source code in the following paths:
- WEBJS:
../whatsapp-web.js - NOWEB:
../WhiskeySockets-Baileys- whatsapp-rust-bridge -
../whatsapp-rust-bridge
- whatsapp-rust-bridge -
- GOWS:
../gows- whatsmeow -
../whatsmeow
- whatsmeow -
- WPP:
../wa-js,../wppconnect,../wppconnect-server - ChatWoot:
../chatwoot
Following this playbook keeps contributions aligned with WAHA’s structure, automation hooks, and release process.
You can run the project outside of sandbox using the below command, then run
queries against default session (if not asked to do something different) using
curl and X-Api-Key: 666 header.
export DEBUG=1
export WAHA_API_KEY=666
export WAHA_DASHBOARD_PASSWORD=666
export WAHA_DASHBOARD_USERNAME=admin
export WWHATSAPP_SWAGGER_USERNAME=admin
export WHATSAPP_SWAGGER_PASSWORD=666
export WHATSAPP_DEFAULT_ENGINE={WEBJS|WPP|NOWEB|GOWS}
export WAHA_DEBUG_MODE=True
export WAHA_HTTP_STRICT_MODE=1
export WAHA_MEDIA_STORAGE=LOCAL
export WHATSAPP_FILES_FOLDER=./.media
npm run start- Ask user before running the server.
- Before executing some queries make sure the session is in
WORKINGstatus. - If it's
FAILEDorSCAN_QR_CODEask user to scan QR code or fix failed session.
Add @Activity() (from src/core/abc/activity.ts) to every engine method that
makes a network call to WhatsApp servers. It triggers maintainPresenceOnline()
before the method runs, keeping the session ONLINE during API activity and
scheduling an OFFLINE transition after an idle period. Skip it on methods that
only throw NotImplementedByEngineError / AvailableInPlusVersion.