Pre-release candidate for 2.4.0. Built from
develop(commitaf4db31). Not merged tomain. Use for validation only; do not deploy to production without further testing.
2.4.0 (2026-05-06)
⚠️ BREAKING CHANGE — License activation is now required
Starting with v2.4.0, every Evolution API instance must be activated against the
Evolution Foundation licensing server before serving API traffic. Until activation,
all business endpoints return:
HTTP 503 Service Unavailable
{
"error": "service not activated",
"code": "LICENSE_REQUIRED",
"register_url": "https://<your-host>/manager/login",
"instance_id": "<uuid>",
"docs_url": "https://docs.evolutionfoundation.com.br/licensing",
"message": "..."
}
The following routes always remain public so the operator can recover:
/license/status, /license/register, /license/activate, /manager/**,
/health, /server/ok, /ws, static assets.
Migration guide
-
Pull the new version and install dependencies:
git pull npm install
-
Apply the new migration (creates the
RuntimeConfigtable). Required:npm run db:deploy
If you skip this step, the server now fails fast with a clear error
asking you to rundb:deploy. -
Start the service. There are three activation paths:
-
Already have a valid licensing key? Set it as
AUTHENTICATION_API_KEY
in your.envand restart. The bootstrap step will validate the key with
the licensing server, persist it, and activate the instance automatically. -
First-time activation via the manager UI? Open
https://<your-host>/manager/login. The manager detects that the
instance is unlicensed and redirects you to the registration page on
the licensing server. After you complete the form, you are sent back
to/manager/license/callback?code=..., the manager exchanges the code,
and the dashboard becomes accessible. -
Calling the API from code (n8n, Make, custom scripts) without a valid
license? Every request will receive503 LICENSE_REQUIREDwith the
register_urlfield pointing to the manager. Open it in a browser to
activate.
-
Added
Manager v2 — completely redesigned dashboard
The embedded manager (served at /manager) was rebuilt from the ground up
on Tailwind v4 + the new @evoapi/design-system, using the same
visual language as the rest of the Evolution Foundation product line.
Every screen was refactored — no surface remains untouched.
Highlights:
- Modern dashboard with skeleton loading, illustrated empty state,
and a typed-name confirmation modal for instance deletion (no more
accidental clicks). - Dual-provider support: the manager now talks to either
evolution-apiorevolution-go(selected at login, persisted in
localStorage). When connected to a GO backend, the sidebar/router
automatically hide the modules GO does not implement. - Sessions panels for the seven chatbot integrations (OpenAI, Dify,
N8N, EvoAI, EvolutionBot, Flowise, Typebot) gained advanced filters
(name / number / status / time presets + custom),
bulk-status-change actions, client-side pagination, and a real
send-message modal calling/message/sendText. - License-aware login — see the Licensing section below for the
details. - 🧪 Test Interactive modal on each instance card — a 5-tab
payload editor (Reply / CTA / PIX / List / Carousel) for
smoke-testing the new interactive-message endpoints from the
dashboard. Replaces the legacy stand-alonetest-interactive.js
vanilla script that used to be injected intoindex.html. - Full i18n coverage in pt-BR / en-US / es-ES / fr-FR — every
screen, every toast, every modal. - Branding refresh — sidebar/footer/login point to
docs.evolutionfoundation.com.br, GitHub links to
evolution-foundation/evolution-manager-v2, contact to
suporte@evofoundation.com.br.
The new bundle is shipped pre-built under manager/dist/. The manager
source repository moved to evolution-foundation/evolution-manager-v2
(private) — the previous in-repo submodule was dropped.
Licensing
- Licensing module under
src/licensing/— RuntimeContext, gate middleware,
signed/unsigned HTTP transport, hardware-based instance ID, fire-and-forget
heartbeat (every 30 min), graceful shutdown deactivation. Mirrors the
evolution-gopkg/core/reference implementation. - Public license endpoints:
GET /license/status— current activation state and (masked) api_keyGET /license/register?redirect_uri=— initiates registration on the
licensing server, returnsregister_urlGET /license/activate?code=— exchanges the authorization code received
on the callback for a real api_key, persists it, marks the runtime active.
- New Prisma model
RuntimeConfig(key/value rows inRuntimeConfigtable)
for both PostgreSQL and MySQL schemas. - Auto-detect missing migration: if the
RuntimeConfigtable is absent,
the server prints a clear banner explainingnpm run db:deployand exits 1
instead of throwing a stack trace from the Prisma client. - Manager v2 ships with the new license-aware login flow that recognises
HTTP 503 /LICENSE_REQUIRED, calls/license/register, and redirects to
the registration server. After the callback, it lands on
/manager/license/callback?code=...and finalises activation. The new
manager bundle is included undermanager/dist/.
Interactive Messages (Buttons / List / CTA / PIX / Carousel)
- New endpoint
POST /message/sendCarousel/{instance}— multi-card
product carousel built on top ofinteractiveMessage+carouselMessage.
Single-card-without-image falls back tonativeFlowMessagefor iOS
compatibility. New DTOSendCarouselDto, schemacarouselMessageSchema. - Button rendering fixed on WhatsApp Web/Desktop/iOS/Android — removed the
viewOnceMessagewrapper that prevented buttons from rendering and started
injecting the required<biz><interactive type=native_flow v=1> <native_flow v=9 name=mixed/></interactive></biz>node into the
relayMessagestanza via Baileys' officialadditionalNodesoption. - List messages fixed on WhatsApp Web/Desktop — switched to legacy
listMessagewithSINGLE_SELECTlistType (the modern
interactiveMessage + single_selectformat does not render on Web/Desktop)
and added<biz><list type=product_list v=2/></biz>. - Interactive buttons via
deviceSentMessage+ corrected CTA limits
(max 2 CTA buttons, no mixing with reply or PIX), aligning with the
WhatsApp Business message contract. - PIX support for interactive button messages (
payment_infobutton
type — exactly 1 button, isolated). - Quoted product / Catalog
orderMessagesupport — handles
quotedMessage.productMessageand the catalogorderMessageshape,
includinggetTypeMessageenrichment, deduplication cache for
processed order IDs, and propagation through Chatwoot integration. - Manager UI: a
🧪 Test Interactivebutton on each instance card opens
a modal with five tabs (Reply / CTA / PIX / List / Carousel) and an
editable JSON payload — useful for smoke-testing every kind of
interactive message without leaving the dashboard.
History Sync
- New event
messaging-history.setemitted on sync completion, with
cumulative counts (chats, contacts, messages, isLatest, progress).
Allows downstream consumers to know exactly when a history sync has
finished and how much was imported. - Cumulative counters reset on a new sync start to avoid carry-over
between consecutive syncs.
Other
- New endpoint
POST /chat/markMessageAsPlayed/{instance}— emits the
audio "played" receipt (PTT/VOICE), completing the read/delivered/played
triplet for voice messages. - SQS integration now accepts a custom
base_url(useful for
LocalStack and corporate VPC endpoints). - LID → phone-number mapping and caching — translates the new
@lididentifiers WhatsApp uses for hidden-phone profiles into the
real@s.whatsapp.netJID for downstream processing, with a cache
to avoid redundant lookups.
Branding / Documentation
- README, LICENSE, NOTICE, TRADEMARKS standardised under the
Evolution Foundation 2026 identity. - All GitHub URLs migrated from
EvolutionAPItoevolution-foundation. - New README section "License Activation" linking to
https://docs.evolutionfoundation.com.br/licensing.
Fixed
mentionsEveryOnehonoursfalse— earlier the flag was always
applied regardless of value (#2470).getLastMessage: corrected the Prisma JSON path filter so the
query returns the actual last message (#2495 / #2515).markMessageAsRead: corrected JID filter to cover all user types
(regular, business, broadcast, group).- List messages: removed destructive JSON cloning that triggered
this.isZerowhen the message containedLong-typed fields (#2461). - History sync race condition: completion event is now emitted
before the contact upsert, so consumers don't observe the sync as
finished while contacts are still being written (#2510). - Business API (Cloud): race condition in sender identification
resolved (#2493); execution order normalised;chatwootIds
correctly propagated. /instance/logout/{instance}— idempotent: returns SUCCESS instead
of 400 when the instance is already closed, so the manager UI delete
flow (logout-then-delete) does not surface a misleading error.remove.instanceevent — emitted even when logout itself fails,
preventing zombie instances after a partially failed delete (#2520).- Chatbot session: a closed session no longer blocks bot
re-activation. - Docker compose: fresh-install startup failures resolved.
- WhatsApp chats:
accountLidhandling,remoteJidnormalisation
andchatsRawmapping cleaned up to avoid mismatched contact data
on first connection. - Facebook ads:
externalAdReplycontext readability and fallback
path for missing fields. - Networking: added the
--network-family-autoselection-attempt-timeout
flag instart:prodso IPv4/IPv6 races no longer hang the boot on
hosts with broken IPv6. - Trailing slashes on configuration URLs are now tolerated in all
HTTP clients. - Verbose-log fix: undefined
maxRetriesreference inside the
messages.updatehandler.
Notes
AUTHENTICATION_API_KEYkeeps its original meaning (global API key for
business endpoints) and gains a second use as the bootstrap license
key. If the value you have is a real licensing key, activation is silent.
If it is not, the service starts unlicensed and waits for activation via
the manager.- Activation is a one-time operation. The
api_keyis stored in the database
and reused across restarts. The licensing server is only consulted again
for periodic heartbeats (telemetry — non-blocking) and on graceful shutdown
(/v1/deactivate). - If the licensing server is unreachable but the instance has been activated
before, the service continues to serve traffic normally — local DB is the
source of truth for activation state after the first successful call. - Release builds bake the licensing endpoint into the bundle as an XOR-encoded
string via tsupdefine, so the URL never appears as a plain literal in
dist/main.js. Generate the pair withnode tools/encode-url.js <url>and
passLICENSE_ENDPOINT_ENCODED/LICENSE_ENDPOINT_XOR_KEYas Docker
build-args (NOT runtime env vars). Local dev builds use a parts-array
fallback that still avoids a single string literal but is not obfuscated.
Troubleshooting
HTTP 503 LICENSE_REQUIRED— expected before activation. Follow the
migration guide.The table evolution_api.RuntimeConfig does not exist(legacy stack
trace if you somehow bypass the new auto-detect) — runnpm run db:deploy.Global API key not accepted by licensing server: invalid signature—
your existingAUTHENTICATION_API_KEYis not a valid licensing key. Use
the manager UI flow to obtain a new one.- Buttons/list not rendering on WhatsApp Web — make sure you are on
v2.4.0+; the<biz>stanza node and the legacylistMessagepayload
shipped with this release are required for cross-client rendering.