feat: synchronize the node with OpenWA 0.23.3 - #45
Merged
Conversation
Several capabilities the node already ships stopped behaving correctly as the server evolved. All but one failed silently, reporting success while doing something other than what the workflow asked for. * Trigger de-duplication keyed on `deliveryId`, which identifies a delivery attempt and is re-minted whenever the gateway replays a delivery stranded by a crash. Only `idempotencyKey` identifies the event, so the duplicate run the option exists to prevent still happened. It now keys on `idempotencyKey` and falls back to `deliveryId` for a gateway too old to send one. * `checkExists` used its GET purely as an existence probe. A registration that was deactivated, repointed at another URL, or resubscribed to a different event set reported healthy, leaving the trigger activated but permanently silent. It now compares `active`, `url` and `events` and rebuilds on drift. * Message Edit never sent `mentions`. An edit replaces the message content, so the tags are re-applied rather than preserved, and omitting the field stripped the @mentions of the message being edited. * Chat Mark Read never sent `messageIds`. Baileys acknowledges individual messages, so a burst left its earlier messages unread and a restarted session had nothing to acknowledge. * Template Update forwarded a blank `name` or `body`, which the server refuses with a 400. Blank `header` and `footer` are still forwarded, since clearing them is the documented way to remove them. * The webhook secret guard enforced the 16 character floor but not the 255 character ceiling, so an over-long secret failed at the server instead of naming the field. Documentation carried the same drift. The Status Recipients note described the allow-list as an opt-out broadcast on whatsapp-web.js; that engine ignores the list entirely and posts to every contact, which made a stated audience restriction misleading in a way that matters. Corrected alongside the catalog and settings notes, which justified absent operations with server limitations that no longer exist, and the role guidance, which omitted that four System reads need an ADMIN key unrestricted by session.
The node already offered the `presence.update` trigger event, but nothing
could subscribe to a chat's presence, so the event could never fire. Presence
is now its own resource with Subscribe, Get and Set Own Presence.
All three are connection-scoped: the subscription and the account's own
availability live on the socket, so a restart or any automatic reconnect ends
them and nothing on the server re-issues them. The field copy says so and
points at driving them from a `session.status` branch rather than once at
workflow start. Subscribe additionally validates the chat id against the
domain-qualified form its DTO requires, which is stricter than every sibling
route, so a bare phone number is refused in the editor instead of at the server.
Session gains the three routes it was missing:
* Log Out, which asks WhatsApp to unlink the device and clears the stored
credentials. Its description spells out how it differs from Stop and Delete,
and that it needs a running session, so nobody puts a Stop in front of it.
* Get Config and Update Config. The update is a merge, and an absent key keeps
its stored value. Returning an attempt limit to unlimited requires sending an
explicit null, which a collection field cannot express, so -1 is accepted as
that sentinel. Zero stays a real value meaning never reconnect.
* A Proxy URL on Create, which is the only point at which a session's egress
proxy can be set. The scheme is checked in the node so a typo fails in the
editor rather than as a Start that times out after 30 seconds with no QR.
Only `proxyUrl` is exposed. The server also stores a `proxyType`, but neither
engine reads it: the agent is chosen from the URL scheme, so offering the field
would be a control that does nothing.
An empty response body is no longer reported as `{ success: true }` for
anything other than a DELETE. Presence > Get answers 200 with no body when a
chat has reported nothing yet, and that means "nothing yet", not success.
A new test walks the node description and asserts every operation the UI offers
reaches a handler branch. An operation added to a resource's list but forgotten
in its handler previously failed only at runtime in a user's workflow.
Five operations the server has shipped for several releases had no equivalent
in the node, and five fields its send DTOs accept were never sent.
New operations:
* Pin and Unpin, with the three pin windows WhatsApp accepts offered as a
dropdown, since any other value is refused. Unpin never carries a duration:
its DTO does not declare the field, and an undeclared field is a 400.
* Star, whose `star` flag has no server-side default and is therefore always
sent. Turning it off removes the star.
* Vote Poll. The selection is sent even when empty, because that is how a vote
is cleared, and the wire field is `pollMessageId` rather than `messageId`.
Selections are matched against the poll's own option texts, so the field
description warns that a near-miss selects nothing and still reports success.
* Get Media, which downloads a message's stored media as binary. It is served
from the gateway's archive rather than an engine, so it works while the
session is stopped. It reuses the existing output-field parameter rather than
adding a second one.
* Send Product. Its response is `{id, timestamp}` where every other send returns
`{messageId, timestamp}`; the value is passed through unchanged rather than
normalized, so the difference stays visible to the workflow.
New fields on existing operations:
* Quoted Message ID on the nine sends whose DTOs declare it, so a reply can
carry media, a location, a contact or a poll instead of only text. An
unresolvable ID fails the send rather than delivering it unquoted, which the
description says.
* Mentions on Reply, Send Audio, Send Sticker and Send Template, which the
server accepts and the node never sent.
* Link Preview on Send Text and Send Template, as a three-way choice rather
than a boolean, because leaving it to the engine is meaningfully different
from either explicit value and the two engines disagree about which way their
default falls.
* Custom Link Preview on Send Text, rebuilt from its filled fields so an opened
but empty collection is not sent as an object the server refuses. Pairing it
with No Preview is the one combination the server rejects, so the node
refuses that pair with a message naming both fields.
* Address on Send Location, which renders under the location name.
The quotable set and the mentionable set are deliberately separate: Send
Location and Send Contact can quote but cannot tag, and Send Template and Edit
can tag but cannot quote. Each field is gated by operation, because the server
rejects any body field the target DTO does not declare.
Chat gains Archive, Pin, Mute and Clear Messages. Clear Messages is the one chat route that names its chat in the path rather than the body, and it is distinct from Delete: it empties the conversation and keeps it. Mute needed care. The server takes an absolute expiry in epoch milliseconds with no duration form, and it distinguishes an omitted key from an explicit null: omitting it is rejected, while null is how a chat is unmuted. The field is a date picker, and leaving it empty sends null rather than dropping the key. Archive and Pin both carry a flag with no server-side default, so it is always sent. The pin description notes WhatsApp's three-chat cap, since a refused fourth pin comes back as a success response carrying a false flag rather than an error. Contact gains List Blocked, which was missing while Block and Unblock shipped, and addressbook Save and Delete. Save overwrites the whole entry, so the field copy says that leaving Last Name empty clears a stored one; the key itself is omitted rather than sent as an empty string, which would reach the engine as a literal null. Label gains Create or Update, Delete and Get Chats, so a workflow can build the labels it attaches instead of depending on ones created by hand in the app. Three things shaped the UI here: * The label id is caller-chosen, so the write operations take a plain text field rather than the existing picker, which can only offer labels that already exist. * Colour is an index from 0 to 19, not a hex value, and 0 is a real colour. It lives in a collection so that leaving it unset stays distinguishable from choosing the first colour. * Reads and writes run on opposite engines, which a notice on the resource now states, since creating a label you cannot then list is otherwise baffling. Add to Chat sent its label id without checking it was non-empty, so a blank one became an opaque server rejection rather than a message naming the field.
…ration The Trigger has offered a `group.join_request` event whose description points at the membership-request routes, but the node exposed none of them, so a workflow could be woken by a join request and had no way to act on it. Group now has Get, Approve and Reject for that queue. An empty requester list means "act on every pending request", which is why those two operations parse the list directly instead of reusing the participant helper: that helper refuses an empty list, which is exactly the meaningful case here. Group also gains: * Get Join Info, which previews a group from an invite code without joining. It accepts a full invite link and reduces it to the code, like Join already did. * Get, Set and Delete Picture, reusing the existing media resolver so a picture can come from a binary field, a URL or base64 exactly as a profile picture can. * memberAddMode in the settings patch, a field both engines support and Get Settings already returned. Channel gains Create, Delete, Mute, Demote Admin and Transfer Ownership. Delete and the existing Unsubscribe are kept deliberately far apart. Delete destroys the channel for every subscriber and only its owner can do it; Unsubscribe merely unfollows it for this account. The server put them on different verbs and paths so a slip cannot turn one into the other, and the node keeps that separation: distinct operations, no shared parameters, and a notice on Delete saying what it does. Two long-standing gaps in the channel copy are also closed. The Channel ID field now says the picker is populated by a listing that only whatsapp-web.js supports, so a Baileys user knows to supply the ID from an expression rather than wondering why the list is empty. The message limit now carries the hundred-item ceiling the server silently clamps to, instead of accepting a larger number and quietly returning fewer.
…cture removal The node has offered a "Send as Voice Note" toggle on Send Audio for several releases, but nothing in the pipeline transcodes: the gateway forwards the bytes it is given and labels them as Ogg/Opus because that is the default when no type is supplied. Sending an MP3 with that toggle on therefore produced a microphone bubble that would not play, and the declared type described bytes that were not there. A new Media resource closes that. Convert to Voice Note and Convert to Compatible Video return base64 plus the type the bytes now are, which drops straight into Send Audio or the new voice status. Conversion is optional on the server and needs ffmpeg, so Check Availability reports whether the deployment has it, and the field copy says a 503 here means conversion is off or busy rather than a bad request. The convert body is built directly rather than through the shared media resolver, because that resolver also emits a MIME type and this DTO refuses any field it does not declare. Also added: * Status > Send Voice, which nests its media under `audio` the way the image and video statuses nest under theirs, with a notice pointing at the converter. * Call > Create Link, which produces a shareable audio or video call link. Both DTO fields are required, so a blank start time means now rather than an omitted key. The response carries only the link, so the copy says the node cannot report an expiry. * Profile > Delete Picture, which completes a resource that could set a picture but never remove one. The Call handler previously refused every operation but Reject through an early return; it is now a switch, and its comment no longer says the call link route is uncovered.
Every subscribed event started a full workflow execution, even when the workflow's first step was to discard it. The gateway has supported server-side filters for some time, and the action node's Webhook Update already exposed them, but neither Webhook Create nor the Trigger could set one. The Trigger now takes Filters and registers them with the webhook, so a non-matching event is dropped at the gateway. Filters are part of the registration fingerprint, so editing one re-registers on the next activation rather than leaving the stored filter in place. The field copy states the two things that otherwise cost an afternoon: filters only narrow message events, so session, group and call events arrive regardless, and a filtered-out delivery is silent, which looks identical to nothing having happened. Webhook Create gains the same Filters field plus Headers and Retry Count, all three of which Update already had. Create and Update now share one JSON parser rather than each carrying their own. Also in this pass: * Get Message Stats sends a Period. It was locked to the last 24 hours because the query was never sent, so the weekly and monthly windows were unreachable. This route binds its query to a schema, so the key is always sent. * The audit Limit and the channel message Limit carry the ceilings the server silently clamps to, instead of accepting a larger number and quietly returning fewer. * Audit Severity is a dropdown over the three values the server recognises. Action stays free text, since its vocabulary is long and grows, but its description now says an unrecognised value returns an empty page rather than an error, which previously made a typo indistinguishable from no activity. The webhook lifecycle test harness ignored the default argument to getNodeParameter, unlike n8n itself, so a parameter read with a fallback came back undefined in tests only.
Two resources remained unreachable from the node. Automation Rules are the gateway's own autoreply: a matching inbound message is answered by the server, without a round trip through n8n, so an out-of-hours acknowledgement still goes out when the workflow is not running. All five routes are exposed. The cooldown is surfaced with its purpose stated, because it is the guard against two auto-repliers answering each other indefinitely and zero disables it. Match conditions reuse the webhook filter shape, which the field copy points out along with the consequence: they only see message events. The Catalog reads complete the product feature. Send Product shipped in this release, but the product ID it needs had no source inside the node, so a user had to find it elsewhere. Get, List Products and Get Product close that. Both they and Send Product are Baileys only, which the resource notice states rather than leaving a 501 to explain itself. That brings the node to every session-scoped route the server publishes. What is still deliberately absent is the server administration surface: the infra, plugin, ingress and integration controllers, and the Prometheus endpoint, which authenticates with its own token rather than the API key this credential holds.
The operation table listed 101 operations for a node that now offers 157, and the compatibility section described a route surface that stopped at v0.10.9. * The table is regenerated from the node description itself, so it lists every resource and operation the node actually offers and cannot drift again. * The compatibility floor moves to 0.16.0. That is now set by the routes rather than the event catalog: Call > Create Link arrived in v0.16.0, above the 0.15.0 the event list needs. The section names which release each part of the new surface came from, and adds a table of the four optional fields that need a server newer than the floor, since those return a 400 rather than degrading. * The "not offered" note is rewritten. The catalog reads and Send Product are offered now, so what remains deliberately absent is the server administration surface. A new note collects the operations that exist on one engine only, including why the Channel ID dropdown cannot populate on Baileys. * The Trigger section documents server-side filters, with the two caveats that matter: they narrow only message events, and a suppressed delivery is silent. Five operation actions are reworded to satisfy the Creator Portal's sentence case rule, which rejects an apostrophe or an internal capital, and the audit Severity filter is a dropdown over the three values the server recognises instead of a second free-text field duplicating the first.
Eight problems found while tracing the new code paths end to end. **Webhook Test was swallowed by de-duplication.** A test delivery's idempotency key is derived from the webhook id alone, so every test of the same webhook carries an identical key. Keying de-duplication on that value meant only the first test ever reached the workflow, and a manual probe is exactly the thing that must always run. Test deliveries are now exempt. **A Filters value supplied by expression crashed activation.** The field is `type: 'json'`, so an expression-driven value arrives as an object rather than text, and calling `.trim()` on it threw. The ordering made it worse: checkExists had already deleted the working registration by then, leaving the trigger unregistered and unable to reactivate. Both nodes now read the field through one shared helper that accepts either shape. **A filter attached out of band was invisible to the drift check.** `filters` is serialized back by the server, and a filter added to the trigger's own registration suppresses deliveries with no other trace, so it is now compared alongside `active`, `url` and `events`. The comparison ignores key order, since the value round-trips through a JSON column and a reordered but identical filter must not force a re-registration. The config fingerprint normalises the same way, so text and the equivalent object are one configuration. Also: * Media conversion asserts the binary property before reading it, so a wrong field name names the field instead of surfacing as an opaque API error. * Session Update Config refuses a non-numeric attempt cap. `NaN < 0` is false, so it would have serialized to `null` and silently reset the cap to unlimited. * The Filters help text drew the hazard line in the wrong place. Filters narrow only message events, but within that family an `is` condition on a field a given event does not carry suppresses it outright, so a sender filter alongside a Message Ack subscription drops every ack. * Vote Poll now states it is whatsapp-web.js only, and that an option whose text contains a comma has to be given as a JSON array. * Three descriptions corrected: the label ID field is shown for Delete and no longer describes only writing, an empty conditions object is refused rather than matching everything, and the comment about absent catalog reads no longer contradicts the Catalog resource added alongside it.
The route answers 404 for five distinct situations, and because the response is raw bytes the server's own explanation never reaches the workflow. The most surprising one is that media sent by URL is not retrievable at all, since the gateway stores the URL rather than the bytes, while the same send made from a binary field or base64 is. Presence > Subscribe built its error message by interpolating the value it had just rejected, so a malformed id produced an example that was not valid either. The example is now fixed text, and the comment above it states which sibling routes differ and why, rather than implying every other route accepts a bare number.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The node was last extended against OpenWA v0.15.0. The server is now at v0.23.3, and the gap had two halves: capabilities that were never exposed, and shipped capabilities that had quietly stopped behaving correctly as the server evolved.
Correctness against a current server
All of these failed silently except the last, which returned a 400.
deliveryIdper attempt. The trigger de-duplicated ondeliveryId, so the duplicate run the option exists to prevent still happened. It now keys onidempotencyKey, falling back todeliveryIdfor older gateways.checkExistsonly probed for existence. A registration that had been deactivated, repointed at another URL, or resubscribed to a different event set reported healthy, leaving the trigger activated but permanently silent. It now comparesactive,urlandeventsand rebuilds on drift.mentions. An edit replaces the message content, so tags are re-applied rather than preserved; omitting the field stripped the @mentions of the message being edited and returned 200.messageIds. Baileys acknowledges individual messages, so a burst left its earlier messages unread and a restarted session had nothing to acknowledge.name/body, which the server refuses. Blankheader/footerare still forwarded, since clearing them is the documented way to remove them.New capability
The node goes from 101 to 157 operations, covering every session-scoped route the server publishes.
New resources: Presence, Media (server-side conversion), Automation Rule (gateway-side autoreply) and Catalog.
Filled-in resources: message pin/unpin/star/vote-poll/media-download/send-product plus
quotedMessageId, widermentions, link-preview control and a location address; chat archive/mute/pin/clear; contact addressbook writes and the blocklist read; label writes and the label-to-chats query; group membership requests, pictures and join preview; channel create/delete/mute/admin; session logout and config; call links; profile picture removal; voice statuses.Server-side filters are the most consequential addition: the Trigger can now register them, so the gateway drops non-matching events instead of waking a workflow that would discard them.
Notable decisions
proxyTypeis not exposed. The server stores it, but neither engine reads it: the agent is chosen from the URL scheme, so the field would be a control that does nothing.idwhere every other send usesmessageId. It is passed through unchanged rather than normalised, so the difference stays visible./api/metrics, which uses its own bearer token rather than this credential's API key.Compatibility
The floor moves to 0.16.0, now set by the routes rather than the event catalog (Call > Create Link arrived in v0.16.0, above the 0.15.0 the event list needs). Four optional fields need a newer server and are documented in a table; they are opt-in, so leaving them alone keeps the node working at the floor.
No existing operation changes its route, method or body. Two behaviour changes worth naming:
mentionswhen supplied, which is the fix above.Verification
npm run build,npm run lint, 409 tests (up from 299), and the n8n Creator Portal scanner all pass; the committeddist/matches source.A new test walks the node description and asserts every operation the UI offers reaches a handler branch, so an operation added to a resource list but forgotten in its handler now fails in CI rather than at runtime in a user's workflow. Route coverage was checked by executing every UI operation against a recording context and diffing the resulting URLs against
openapi.json.