n8n Node Reference
Exact field-level reference for the two nodes in the @rmyndharis/n8n-nodes-openwa package (v1.0.1): OpenWA (an action node) and OpenWA Trigger (a webhook trigger node). Use it to look up every resource, operation, field, and event, plus the HMAC signature the trigger verifies.
Nineteen operations answer a bare JSON array. On node version 2, the version a newly added node gets, each row becomes its own item, so {{ $json.id }} reads a row directly and an empty list emits zero items, which stops the next node unless Always Output Data is on. A node saved on any release up to and including 1.0.0 stays on node version 1 across the package upgrade and keeps the whole array on a single item, so an existing {{ $json[0].id }} expression still works there. A node added while on 1.0.0 carries version 1 too, so it returns to the whole array on upgrade; delete and re-add it to move it to version 2.
See Output and error handling for the affected operations, the ones that keep their shape on both versions, and what each workflow pattern becomes on version 2.
For installation and a first workflow, see the n8n integration guide. For the underlying HTTP API each operation calls, see the API Reference.
Package overview
| Node | n8n type | Direction | Purpose |
|---|---|---|---|
| OpenWA | openWa | Action | Send messages and manage sessions, chats, contacts, groups, labels, templates, status, channels, keys, and webhooks |
| OpenWA Trigger | openWaTrigger | Trigger | Start a workflow when a subscribed event arrives |
Both nodes use the openWaApi credential and call your OpenWA server's /api endpoints.
Credential: OpenWA API
Both nodes require an OpenWA API credential with two fields.
| Field | Required | Description |
|---|---|---|
| Server URL | Yes | Base URL with no trailing slash and no /api suffix, for example http://localhost:2785 or https://wa.example.com. The node appends /api/... itself. |
| API Key | Yes | Sent as the X-API-Key header on every request. Get it from your OpenWA dashboard. |
The credential test issues an authenticated GET /api/sessions, so an invalid key fails at save time. To create the key, see Authentication.
OpenWA (action node)
Package 1.0.1 covers the nineteen resources below. Select a
Resource, then an Operation; the visible fields change with each operation.
Most operations take a Session Name or ID. It is a dropdown: the node calls GET /api/sessions
with your credential and lists each session as name (id), storing the session's UUID. The stored
value is a plain string, so an expression can supply an id instead of picking one. The same pattern
backs the Chat, Contact, Group, Label, Template, Channel, and Webhook ID
fields, each loading from its own listing endpoint once a session is chosen, and the API Key
field, which is not session-scoped. The session, chat, contact, and group loaders fetch one page of
up to 1000 entries — beyond that, set the field from an expression.
The Observability and API Key resources have no Session ID at all; on System it appears only for Get Session Stats, and on Webhook it is hidden for List All and Get Delivery Failures.
| Resource | Operation | Method + path |
|---|---|---|
| Session | Create | POST /api/sessions |
| Session | Start | POST /api/sessions/{sessionId}/start |
| Session | Stop | POST /api/sessions/{sessionId}/stop |
| Session | Force Kill | POST /api/sessions/{sessionId}/force-kill |
| Session | Delete | DELETE /api/sessions/{sessionId} |
| Session | Get QR | GET /api/sessions/{sessionId}/qr |
| Session | Request Pairing Code | POST /api/sessions/{sessionId}/pairing-code |
| Session | Get Status | GET /api/sessions/{sessionId} |
| Session | List All | GET /api/sessions |
| Session | Get Stats Overview | GET /api/sessions/stats/overview |
| Session | Log Out | POST /api/sessions/{sessionId}/logout |
| Session | Get Config | GET /api/sessions/{sessionId}/config |
| Session | Update Config | PATCH /api/sessions/{sessionId}/config |
| Session | Get Proxy | GET /api/sessions/{sessionId}/proxy |
| Session | Update Proxy | PATCH /api/sessions/{sessionId}/proxy |
| Message | Send Text | POST /api/sessions/{sessionId}/messages/send-text |
| Message | Send Image | POST /api/sessions/{sessionId}/messages/send-image |
| Message | Send Video | POST /api/sessions/{sessionId}/messages/send-video |
| Message | Send Audio | POST /api/sessions/{sessionId}/messages/send-audio |
| Message | Send Document | POST /api/sessions/{sessionId}/messages/send-document |
| Message | Send Sticker | POST /api/sessions/{sessionId}/messages/send-sticker |
| Message | Send Location | POST /api/sessions/{sessionId}/messages/send-location |
| Message | Send Contact | POST /api/sessions/{sessionId}/messages/send-contact |
| Message | Send Poll | POST /api/sessions/{sessionId}/messages/send-poll |
| Message | Send Template | POST /api/sessions/{sessionId}/messages/send-template |
| Message | Send Bulk | POST /api/sessions/{sessionId}/messages/send-bulk |
| Message | Get Batch Status | GET /api/sessions/{sessionId}/messages/batch/{batchId} |
| Message | Cancel Batch | POST /api/sessions/{sessionId}/messages/batch/{batchId}/cancel |
| Message | Reply | POST /api/sessions/{sessionId}/messages/reply |
| Message | React | POST /api/sessions/{sessionId}/messages/react |
| Message | Edit | POST /api/sessions/{sessionId}/messages/edit |
| Message | Delete | POST /api/sessions/{sessionId}/messages/delete |
| Message | Forward | POST /api/sessions/{sessionId}/messages/forward |
| Message | List | GET /api/sessions/{sessionId}/messages |
| Message | Get History | GET /api/sessions/{sessionId}/messages/{chatId}/history |
| Message | Get Reactions | GET /api/sessions/{sessionId}/messages/{chatId}/{messageId}/reactions |
| Message | Get Media | GET /api/sessions/{sessionId}/messages/{chatId}/{messageId}/media |
| Message | Pin | POST /api/sessions/{sessionId}/messages/pin |
| Message | Unpin | POST /api/sessions/{sessionId}/messages/unpin |
| Message | Star | POST /api/sessions/{sessionId}/messages/star |
| Message | Vote Poll | POST /api/sessions/{sessionId}/messages/vote-poll |
| Message | Send Product | POST /api/sessions/{sessionId}/messages/send-product |
| Contact | Check Exists | GET /api/sessions/{sessionId}/contacts/check/{phoneNumber} |
| Contact | Get Info | GET /api/sessions/{sessionId}/contacts/{contactId} |
| Contact | Get Phone | GET /api/sessions/{sessionId}/contacts/{contactId}/phone |
| Contact | Get Profile Picture | GET /api/sessions/{sessionId}/contacts/{contactId}/profile-picture |
| Contact | Get Profile Pictures | GET /api/sessions/{sessionId}/contacts/profile-pictures?ids= |
| Contact | List | GET /api/sessions/{sessionId}/contacts |
| Contact | List Blocked | GET /api/sessions/{sessionId}/contacts/blocked |
| Contact | Save | PUT /api/sessions/{sessionId}/contacts/{contactId} |
| Contact | Delete | DELETE /api/sessions/{sessionId}/contacts/{contactId} |
| Contact | Block | POST /api/sessions/{sessionId}/contacts/{contactId}/block |
| Contact | Unblock | DELETE /api/sessions/{sessionId}/contacts/{contactId}/block |
| Chat | List | GET /api/sessions/{sessionId}/chats |
| Chat | Mark Read | POST /api/sessions/{sessionId}/chats/read |
| Chat | Mark Unread | POST /api/sessions/{sessionId}/chats/unread |
| Chat | Delete | POST /api/sessions/{sessionId}/chats/delete |
| Chat | Clear Messages | DELETE /api/sessions/{sessionId}/chats/{chatId}/messages |
| Chat | Archive | POST /api/sessions/{sessionId}/chats/archive |
| Chat | Pin | POST /api/sessions/{sessionId}/chats/pin |
| Chat | Mute | POST /api/sessions/{sessionId}/chats/mute |
| Chat | Set State | POST /api/sessions/{sessionId}/chats/typing |
| Group | List | GET /api/sessions/{sessionId}/groups |
| Group | Create | POST /api/sessions/{sessionId}/groups |
| Group | Join | POST /api/sessions/{sessionId}/groups/join |
| Group | Get Join Info | GET /api/sessions/{sessionId}/groups/join-info?code= |
| Group | Get | GET /api/sessions/{sessionId}/groups/{groupId} |
| Group | Leave | POST /api/sessions/{sessionId}/groups/{groupId}/leave |
| Group | Add Participants | POST /api/sessions/{sessionId}/groups/{groupId}/participants |
| Group | Remove Participants | DELETE /api/sessions/{sessionId}/groups/{groupId}/participants |
| Group | Promote Participants | POST /api/sessions/{sessionId}/groups/{groupId}/participants/promote |
| Group | Demote Participants | POST /api/sessions/{sessionId}/groups/{groupId}/participants/demote |
| Group | Update Subject | PUT /api/sessions/{sessionId}/groups/{groupId}/subject |
| Group | Update Description | PUT /api/sessions/{sessionId}/groups/{groupId}/description |
| Group | Get Settings | GET /api/sessions/{sessionId}/groups/{groupId}/settings |
| Group | Update Settings | PUT /api/sessions/{sessionId}/groups/{groupId}/settings |
| Group | Get Invite Code | GET /api/sessions/{sessionId}/groups/{groupId}/invite-code |
| Group | Revoke Invite Code | POST /api/sessions/{sessionId}/groups/{groupId}/invite-code/revoke |
| Group | Get Membership Requests | GET /api/sessions/{sessionId}/groups/{groupId}/membership-requests |
| Group | Approve Membership Requests | POST /api/sessions/{sessionId}/groups/{groupId}/membership-requests/approve |
| Group | Reject Membership Requests | POST /api/sessions/{sessionId}/groups/{groupId}/membership-requests/reject |
| Group | Get Picture | GET /api/sessions/{sessionId}/groups/{groupId}/picture |
| Group | Set Picture | PUT /api/sessions/{sessionId}/groups/{groupId}/picture |
| Group | Delete Picture | DELETE /api/sessions/{sessionId}/groups/{groupId}/picture |
| Profile | Set Name | PUT /api/sessions/{sessionId}/profile/name |
| Profile | Set Status | PUT /api/sessions/{sessionId}/profile/status |
| Profile | Set Picture | PUT /api/sessions/{sessionId}/profile/picture |
| Profile | Delete Picture | DELETE /api/sessions/{sessionId}/profile/picture |
| Label | List | GET /api/sessions/{sessionId}/labels |
| Label | Get | GET /api/sessions/{sessionId}/labels/{labelId} |
| Label | Create or Update | PUT /api/sessions/{sessionId}/labels/{labelId} |
| Label | Delete | DELETE /api/sessions/{sessionId}/labels/{labelId} |
| Label | Get Chats | GET /api/sessions/{sessionId}/labels/{labelId}/chats |
| Label | Get for Chat | GET /api/sessions/{sessionId}/labels/chat/{chatId} |
| Label | Add to Chat | POST /api/sessions/{sessionId}/labels/chat/{chatId} |
| Label | Remove From Chat | DELETE /api/sessions/{sessionId}/labels/chat/{chatId}/{labelId} |
| Status | List | GET /api/sessions/{sessionId}/status |
| Status | Get by Contact | GET /api/sessions/{sessionId}/status/{contactId} |
| Status | Get Media | GET /api/sessions/{sessionId}/status/{statusId}/media |
| Status | Delete | DELETE /api/sessions/{sessionId}/status/{statusId} |
| Status | Send Text | POST /api/sessions/{sessionId}/status/send-text |
| Status | Send Image | POST /api/sessions/{sessionId}/status/send-image |
| Status | Send Video | POST /api/sessions/{sessionId}/status/send-video |
| Status | Send Voice | POST /api/sessions/{sessionId}/status/send-voice |
| Template | List | GET /api/sessions/{sessionId}/templates |
| Template | Create | POST /api/sessions/{sessionId}/templates |
| Template | Get | GET /api/sessions/{sessionId}/templates/{templateId} |
| Template | Update | PUT /api/sessions/{sessionId}/templates/{templateId} |
| Template | Delete | DELETE /api/sessions/{sessionId}/templates/{templateId} |
| Channel | List | GET /api/sessions/{sessionId}/channels |
| Channel | Get | GET /api/sessions/{sessionId}/channels/{channelId} |
| Channel | Get Messages | GET /api/sessions/{sessionId}/channels/{channelId}/messages |
| Channel | Create | POST /api/sessions/{sessionId}/channels |
| Channel | Subscribe | POST /api/sessions/{sessionId}/channels/subscribe |
| Channel | Unsubscribe | DELETE /api/sessions/{sessionId}/channels/{channelId} |
| Channel | Delete | POST /api/sessions/{sessionId}/channels/{channelId}/delete |
| Channel | Mute | POST /api/sessions/{sessionId}/channels/{channelId}/mute |
| Channel | Demote Admin | POST /api/sessions/{sessionId}/channels/{channelId}/admins/demote |
| Channel | Transfer Ownership | POST /api/sessions/{sessionId}/channels/{channelId}/owner/transfer |
| Call | Reject | POST /api/sessions/{sessionId}/calls/{callId}/reject |
| Call | Create Link | POST /api/sessions/{sessionId}/calls/link |
| Observability | Check | GET /api/health |
| Observability | Check Liveness | GET /api/health/live |
| Observability | Check Readiness | GET /api/health/ready |
| System | Get Settings | GET /api/settings |
| System | Get Stats Overview | GET /api/stats/overview |
| System | Get Message Stats | GET /api/stats/messages |
| System | Get Session Stats | GET /api/stats/sessions/{sessionId} |
| System | Get Audit Log | GET /api/audit |
| System | Search | GET /api/search |
| API Key | List | GET /api/auth/api-keys |
| API Key | Create | POST /api/auth/api-keys |
| API Key | Get | GET /api/auth/api-keys/{keyId} |
| API Key | Update | PUT /api/auth/api-keys/{keyId} |
| API Key | Revoke | POST /api/auth/api-keys/{keyId}/revoke |
| API Key | Delete | DELETE /api/auth/api-keys/{keyId} |
| API Key | Validate | POST /api/auth/validate |
| Webhook | Create | POST /api/sessions/{sessionId}/webhooks |
| Webhook | Get | GET /api/sessions/{sessionId}/webhooks/{webhookId} |
| Webhook | List | GET /api/sessions/{sessionId}/webhooks |
| Webhook | Update | PUT /api/sessions/{sessionId}/webhooks/{webhookId} |
| Webhook | Delete | DELETE /api/sessions/{sessionId}/webhooks/{webhookId} |
| Webhook | Test | POST /api/sessions/{sessionId}/webhooks/{webhookId}/test |
| Webhook | List All | GET /api/webhooks |
| Webhook | Get Delivery Failures | GET /api/webhooks/delivery-failures |
| Automation Rule | List | GET /api/sessions/{sessionId}/automation-rules |
| Automation Rule | Create | POST /api/sessions/{sessionId}/automation-rules |
| Automation Rule | Get | GET /api/sessions/{sessionId}/automation-rules/{ruleId} |
| Automation Rule | Update | PUT /api/sessions/{sessionId}/automation-rules/{ruleId} |
| Automation Rule | Delete | DELETE /api/sessions/{sessionId}/automation-rules/{ruleId} |
| Catalog | Get | GET /api/sessions/{sessionId}/catalog |
| Catalog | List Products | GET /api/sessions/{sessionId}/catalog/products |
| Catalog | Get Product | GET /api/sessions/{sessionId}/catalog/products/{productId} |
| Media | Check Availability | GET /api/sessions/{sessionId}/media/convert |
| Media | Convert to Voice Note | POST /api/sessions/{sessionId}/media/convert/voice |
| Media | Convert to Video | POST /api/sessions/{sessionId}/media/convert/video |
| Presence | Subscribe | POST /api/sessions/{sessionId}/presence/subscribe |
| Presence | Get | GET /api/sessions/{sessionId}/presence/{chatId} |
| Presence | Set Own Presence | PUT /api/sessions/{sessionId}/presence |
Values that land in a URL path — Session ID, Batch ID, Webhook ID, Label ID, Rule ID, Status ID,
Template ID, Call ID, API key ID — are sanitized before use: they cannot be empty and cannot contain
.., /, or \. A value that violates this fails the item with a clear error rather than building
a malformed URL. Values that legitimately carry such characters are checked for emptiness and
URL-encoded instead: the WhatsApp JIDs (chat, contact, group, channel), the Message ID a reaction or
media read addresses, and the Product ID from a catalog listing.
Send Catalog and PUT /api/settings were removed from the server in v0.19.0 — both had answered 501 since they shipped. Settings are environment-derived and read-only at runtime, so System exposes only a read, and /api/metrics authenticates with its own bearer token rather than the X-API-Key this credential carries. The Catalog resource reads a Baileys session's catalog; on a whatsapp-web.js session every catalog operation answers 501. Sending a product from that catalog is Message → Send Product, not a Catalog operation.
Separately, the node is not a full mirror of the API. Four server areas have no resource: plugin
management (/api/plugins/*), the infrastructure routes (/api/infra/*), Integration
Fabric instance management (/api/integration/*), and the Prometheus metrics scrape. Within
the resources it does cover, the operation tables above are the authoritative list: an operation
absent from them is absent from the node. Reach anything missing with n8n's HTTP Request node
using the same credential.
Media source model
Every operation that carries media — Message → Send Image, Send Video, Send Audio, Send Document and Send Sticker; Profile → Set Picture; Group → Set Picture; and Status → Send Image, Send Video and Send Voice — shares one source model. A Source field offers three ways to supply the bytes, and the remaining fields follow from it:
| Source | Extra fields | Request fields sent |
|---|---|---|
| Binary Data | Binary Property (default data) | base64 (the buffer encoded) and mimetype, read from the binary metadata |
| URL | (the media URL field) | url |
| Base64 | Base64 Data, MIME Type | base64 and mimetype (the MIME Type field) |
Two of them differ in their labels. Group → Set Picture and Status → Send Voice name the
first option Binary rather than Binary Data, default to it rather than to URL, and call the
field that follows Input Binary Field rather than Binary Property. Everywhere else the option
reads Binary Data, the default is URL, and the field is Binary Property. Media → Convert
follows the same shorter naming; see Media resource.
When a binary item carries no MIME type of its own, the node falls back to a per-kind default:
application/octet-stream for images, video, and documents, audio/ogg; codecs=opus for audio and
status voice notes, image/webp for stickers, image/jpeg for profile pictures, group pictures and
status images, and video/mp4 for status video.
OpenWA rejects base64 media without a mimetype. The Binary Data source fills it in from the
binary metadata and the URL source needs nothing extra, but the Base64 source requires you to
set the MIME Type field.
List fields
Several fields take a list: Mentions, Participants, Recipients, Contact IDs, poll Options, Allowed IPs, and Allowed Sessions. Each is a plain string field rather than a fixed set of rows, so it accepts three shapes: a comma- or newline-separated list typed by hand, a pasted JSON array, or an expression resolving to an array. Blank entries are dropped.
Session resource
Session operations address a session by its UUID. Create, List All, and Get Stats Overview take no Session ID.
| Operation | Fields | Notes |
|---|---|---|
| Create | Session Name, Session Config (JSON), Proxy URL | Session Name is required: 3–50 characters, letters, numbers, and hyphens only. Session Config is an optional JSON object, for example {"autoRejectCalls":true}; a non-object value fails the item. Proxy URL is optional and validated the same way Update Proxy validates it, but a blank value is omitted rather than sent, since the server's DTO reads a blank as a malformed URL. Returns the new session's UUID. |
| Start | Session Name or ID | Starts the session and connects to WhatsApp. |
| Stop | Session Name or ID | Stops the session and disconnects. |
| Force Kill | Session Name or ID | Force-kills a stuck session's engine. |
| Delete | Session Name or ID | Deletes the session. |
| Get QR | Session Name or ID | Returns the QR code for scanning authentication. |
| Request Pairing Code | Session Name or ID, Phone Number | Phone Number is 6–15 digits in international format (for example 628123456789); the node strips +, spaces, -, and () before validating. Returns an 8-character phone-linking code. Start the session and poll Get Status until status reads qr_ready before requesting a code: an unstarted session answers 400, a started one that has not reached qr_ready answers 409, and one that already reads ready is linked and answers 400. See Sessions. |
| Get Status | Session Name or ID | Returns the status of one session. |
| List All | Options: Limit, Offset | Returns every session, paginated. |
| Get Stats Overview | (none) | Overview statistics across all sessions. |
| Log Out | Session Name or ID | Asks WhatsApp to remove this companion device, then tears the engine down. Unlike Stop it needs a live engine, and the session must be paired again afterwards. |
| Get Config | Session Name or ID | The session's stored configuration. |
| Update Config | Session Name or ID, Config Fields | A PATCH: only the fields you add are sent, and anything left out keeps its stored value. At least one is required. |
| Get Proxy | Session Name or ID | Server v0.23.4 and newer. Reports the scheme, the host and port, and whether credentials are embedded. It never returns the credentials themselves. |
| Update Proxy | Session Name or ID, Proxy URL, Clear Proxy | Server v0.23.4 and newer. Writes the proxy without restarting anything, so a change takes effect on the next Start. Refuses a session-scoped key. |
Session Config on Create takes the same three keys the collection below offers, spelled as JSON:
autoRejectCalls, maxReconnectAttempts and reconnectBaseDelay. Those are the only keys the
server reads. Anything else is stored and ignored, so a misspelled key is accepted silently and
never takes effect.
The Config Fields collection on Update Config:
| Field | Type | Notes |
|---|---|---|
| Auto Reject Calls | boolean | Decline every incoming call automatically. Re-read on each call, so it applies immediately. |
| Max Reconnect Attempts | number | How many consecutive reconnects to attempt, -1 to 20. -1 means unlimited and is both the default and the only way back to it once a cap is set; the node sends it as the null the server spells unlimited with. 0 is a real value meaning never reconnect, leaving the session down until it is started by hand. Applies from the next Start, not to a reconnect already under way. |
| Reconnect Base Delay (Ms) | number | Base backoff between attempts, 1000 to 300000. Applies from the next Start. |
Proxy URL is a full URL with its scheme (http, https, socks4 or socks5), at most 255
characters; a value without a scheme or without a host fails the item. Credentials embedded in the
URL work on Baileys, but whatsapp-web.js cannot authenticate a SOCKS proxy. An unreachable proxy does
not fail fast: no QR is ever delivered and Start times out after about 30 seconds.
Clear Proxy removes the stored proxy instead of setting one, because the server spells removal as
an explicit null that a text field cannot express. While it is on, Proxy URL is ignored. The node
tests it against true rather than for truthiness, so an expression resolving to the string
"false" does not wipe a working proxy. With the toggle off, a blank Proxy URL fails the item rather
than being sent, which would otherwise turn the PATCH into a no-op read reporting the old proxy as
though it had been written.
Message resource
The twenty-seven message operations share a required Session Name or ID. All of them except Send Bulk, Get Batch Status, Cancel Batch, Forward, and List also require a Chat Name or ID — the batch routes are not addressed to one chat, Forward names a source and a target chat of its own, and List takes an optional chat filter in its options collection.
| Field | Type | Required | Description |
|---|---|---|---|
| Session Name or ID | options | Yes | The session to act through. |
| Chat Name or ID | options | Yes (all except Send Bulk, Get Batch Status, Cancel Batch, Forward, List) | Recipient, for example 628123456789@c.us for a person or ...@g.us for a group. Empty values fail the item. |
The operation-specific fields follow.
Send Text — request body { chatId, text }.
| Field | Type | Required | Maps to |
|---|---|---|---|
| Message | string | Yes | text |
| Link Preview | options | No | linkPreview. Baileys builds a preview only when this asks for one; whatsapp-web.js builds one by default and this suppresses it. |
| Custom Link Preview | collection | No | customLinkPreview. Supplies the card yourself, so nothing is fetched. Offers URL, Title and Description; the first two are both required or nothing is sent, and the URL must also appear literally in the message text or WhatsApp renders no card at all. Baileys only: whatsapp-web.js answers 501 rather than dropping it silently. |
Send Image — the media source model, plus an optional caption. Request
body { chatId } plus the source fields, and caption when set.
| Field | Type | Required | Notes |
|---|---|---|---|
| Image Source | options: Binary Data, URL, Base64 | Yes | Default URL. |
| Binary Property | string | Yes (binary) | Default data. |
| Image URL | string | Yes (url) | Public URL of the image. |
| Base64 Data | string | Yes (base64) | Base64-encoded image bytes. |
| MIME Type | string | Yes (base64) | For example image/png. Default image/jpeg. |
| Caption | string | No | Sent as caption. |
Send Video — same shape as Send Image, with the Video Source field and a video/mp4 default
MIME type.
Send Audio — same source model, plus a voice-note toggle. Request body { chatId } plus the
source fields, and ptt: true when the toggle is on.
| Field | Type | Required | Notes |
|---|---|---|---|
| Audio Source | options: Binary Data, URL, Base64 | Yes | Default URL. |
| Binary Property | string | Yes (binary) | Default data. |
| Audio URL | string | Yes (url) | Public URL of the audio. |
| Base64 Data | string | Yes (base64) | Base64-encoded audio bytes. |
| MIME Type | string | Yes (base64) | Default audio/ogg; codecs=opus. For a plain audio file set its real type (for example audio/mpeg). |
| Send as Voice Note | boolean | No | Default off. When on, the clip is delivered as a true WhatsApp voice note (PTT — the microphone bubble with a waveform) instead of a plain audio file. Requires OGG/Opus audio (audio/ogg; codecs=opus). |
Send Document — same source model, with a filename. Request body { chatId, filename } plus the
source fields, and caption when set.
| Field | Type | Required | Notes |
|---|---|---|---|
| Document Source | options: Binary Data, URL, Base64 | Yes | Default URL. |
| Binary Property | string | Yes (binary) | Default data. |
| Document URL | string | Yes (url) | Public URL of the document. |
| Base64 Data | string | Yes (base64) | Base64-encoded document bytes. |
| MIME Type | string | Yes (base64) | For example application/pdf. Default application/pdf. |
| Filename | string | No | Sent as filename. Default document.pdf. |
| Caption | string | No | Sent as caption when set. |
Send Sticker — same source model. WhatsApp expects a WebP sticker, ideally 512×512.
| Field | Type | Required | Notes |
|---|---|---|---|
| Sticker Source | options: Binary Data, URL, Base64 | Yes | Default URL. |
| Binary Property | string | Yes (binary) | Default data. |
| Sticker URL | string | Yes (url) | Public URL of the sticker (WhatsApp expects a WebP image). |
| Base64 Data | string | Yes (base64) | Base64-encoded sticker bytes. |
| MIME Type | string | Yes (base64) | Default image/webp; WhatsApp requires WebP. |
Send Text, Send Image, Send Video, Send Audio, Send Document, Send Sticker, Send Template, Reply and Edit all accept an optional Mentions field — a list
of WhatsApp ids to @mention (for example 628123456789@c.us), in any of the shapes described under
List fields. The message text or caption must also contain the matching
@628123456789 token for the mention to render. The field is guarded by operation, so a mentions
value can never ride along on an operation whose DTO would reject it.
Send Text, Send Image, Send Video, Send Audio, Send Document, Send Sticker, Send Location, Send
Contact and Send Poll each accept an optional Quoted Message ID, sent as quotedMessageId. It is
how a reply carries media, a location, a contact or a poll: Reply itself sends text only, and its
own Quoted Message ID is required rather than optional. The two sets are not the same, and neither
follows from the other. Send Template and Edit can mention but cannot quote.
An id the engine cannot resolve fails the send outright rather than delivering it unquoted, and Baileys can only quote a message it has already stored.
Send Location — request body { chatId, latitude, longitude }.
| Field | Type | Required | Maps to |
|---|---|---|---|
| Latitude | number | Yes | latitude |
| Longitude | number | Yes | longitude |
| Location Name | string | No | Sent as description (OpenWA's field for the location label) when set. |
| Address | string | No | Sent as address, the street line WhatsApp shows under the label. |
Send Contact — request body { chatId, contactName, contactNumber }.
| Field | Type | Required | Maps to |
|---|---|---|---|
| Contact Name | string | Yes | contactName — display name for the shared contact card. |
| Contact Number | string | Yes | contactNumber — phone number including the country code (it is not auto-prefixed), for example +628123456789. |
Send Poll — request body { chatId, name, options }, plus allowMultipleAnswers when on.
| Field | Type | Required | Notes |
|---|---|---|---|
| Question | string | Yes | Sent as name. Maximum 255 characters. |
| Options | string list | Yes | The answers to vote on. Between 2 and 12 — anything outside that range fails the item. Each option is also capped at 100 characters server-side. |
| Allow Multiple Answers | boolean | No | Default off. When on, voters may pick several options instead of exactly one. |
Send Template — renders a stored template and sends the result. Request
body { chatId } plus either templateId or templateName, and vars when set.
| Field | Type | Required | Notes |
|---|---|---|---|
| Template Name or ID | options | Yes (or Template Name) | Id of the template to render. When both this and Template Name are set, the id wins. |
| Template Name | string | Yes (or Template Name or ID) | Used only when the id is empty. Providing neither fails the item. |
| Variables (JSON) | json | No | Values substituted into the template's {{placeholder}} tokens, as a JSON object such as {"name":"Alice"}. A non-object value fails the item. |
Send Bulk — sends up to 100 messages as a tracked batch. Request body { messages }, plus
batchId and options when set. No Chat ID — each item carries its own chatId.
| Field | Type | Required | Notes |
|---|---|---|---|
| Messages (JSON) | json | Yes | Array of 1–100 items — an empty array or more than 100 items fails the item. A text item is { "chatId": "628123456789@c.us", "type": "text", "content": { "text": "Hello" } }. A media item nests the media object under the type key (image/video/audio/document) and puts caption alongside it on content: { "chatId": "628123456789@c.us", "type": "image", "content": { "image": { "url": "https://example.com/a.jpg" }, "caption": "Hi" } }. Media uses url or base64 (add mimetype for base64) — there is no binary source in bulk. |
| Batch ID | string | No | Custom batch id; must be unique per session. Leave empty to let the server generate one. |
| Options | collection | No | Left empty, the server applies its own defaults (delay 3000 ms, randomize on, stop-on-error off). |
The Options collection offers:
| Option | Type | Default | Notes |
|---|---|---|---|
| Delay Between Messages (Ms) | number | 3000 | Milliseconds to wait between sends, 1000–60000. |
| Randomize Delay | boolean | on | Adds a random 0–2000 ms on top of the delay. |
| Stop on Error | boolean | off | Aborts the batch on the first failed send. |
Send Bulk returns a batchId immediately and sends in the background. Poll Get Batch Status
until the status is completed, cancelled, or failed, or stop the batch early with Cancel
Batch.
Get Batch Status / Cancel Batch — read a batch's progress or cancel it. Each takes one field:
| Field | Type | Required | Notes |
|---|---|---|---|
| Batch ID | string | Yes | The batch id returned by Send Bulk. |
Reply — request body { chatId, quotedMessageId, text }.
| Field | Type | Required | Notes |
|---|---|---|---|
| Quoted Message ID | string | Yes | Full serialized id of the message to quote (for example true_628123456789@c.us_3EB0…), as returned by send operations or delivered by the Trigger. |
| Message | string | Yes | The reply text, sent as text. |
React — request body { chatId, messageId, emoji }.
| Field | Type | Required | Notes |
|---|---|---|---|
| Message ID | string | Yes | Full serialized id of the target message. |
| Emoji | string | No | The emoji to react with. Leave empty to remove your existing reaction — the empty value is sent deliberately. |
Edit — request body { chatId, messageId, body }.
| Field | Type | Required | Notes |
|---|---|---|---|
| Message ID | string | Yes | Full serialized id of the message to edit. |
| Message | string | Yes | The replacement body, maximum 4096 characters. |
Delete — request body { chatId, messageId, forEveryone }.
| Field | Type | Required | Notes |
|---|---|---|---|
| Message ID | string | Yes | Full serialized id of the message to delete. |
| Delete for Everyone | boolean | No | Default on — revokes the message for everyone. Turn off to remove only your own local copy. |
Forward — request body { fromChatId, toChatId, messageId }. Takes no shared Chat ID.
| Field | Type | Required | Notes |
|---|---|---|---|
| From Chat Name or ID | options | Yes | The chat the message currently lives in. |
| To Chat Name or ID | options | Yes | The chat to forward it to. |
| Message ID | string | Yes | Full serialized id of the message to forward. |
List — reads stored messages. Takes no shared Chat ID; every filter lives in its Options collection and is sent as a query parameter.
| Option | Type | Default | Notes |
|---|---|---|---|
| After Row ID | string | unset | Keyset cursor: the id of the previous page's last message. Anchors the page to a row rather than a count, so a message arriving mid-walk cannot repeat or skip one. Takes precedence over Offset. |
| Chat Name or ID | options | — | Only return messages from this chat. |
| From | string | — | Only return messages from this sender. |
| Inline Media | boolean | true | Turn off to omit inline media payloads, keeping each row's { omitted, sizeBytes } marker. The media budget is per response, so a paged walk pays it again on every page. |
| Limit | number | 50 | Max results, 1 to 100. |
| Offset | number | 0 | Records to skip before collecting the result set. |
Unlike Get History and Get Reactions beside it, List answers an envelope ({ messages, total })
and so still produces a single item; see Output and error handling.
Get History reads a chat's history from the device. whatsapp-web.js only; Baileys answers
501, so on a Baileys session reach for List, which serves the gateway's own stored copy on
both engines. Requires a Chat ID; the Options collection offers:
| Option | Type | Default | Notes |
|---|---|---|---|
| Deep | boolean | off | Pull older messages from the device instead of only what the server has stored. |
| Include Media | boolean | off | Include media payloads in the returned messages. |
| Limit | number | 50 | Max number of results to return. |
Get Reactions reads the reactions on one message. whatsapp-web.js only; Baileys answers
501. The Trigger's message.reaction event fires on both engines, so a Baileys workflow tracks
reactions as they arrive rather than reading them back.
| Field | Type | Required | Notes |
|---|---|---|---|
| Chat Name or ID | options | Yes | The chat the message lives in. |
| Message ID | string | Yes | Full serialized id of the message. |
Get Media downloads the media attached to one message.
| Field | Type | Required | Notes |
|---|---|---|---|
| Chat Name or ID | options | Yes | The chat the message lives in. |
| Message ID | string | Yes | Full serialized id of the message. |
| Put Output in Field | string | Yes | Name of the output binary property, default data. |
The bytes are attached as a binary property rather than put on json, so the item's json is empty.
This one reads from the gateway's own archive rather than the engine, so it works while the session
is stopped, and a 404 is its only failure.
Pin and Unpin take request body { chatId, messageId }, plus durationSeconds on Pin alone.
| Field | Type | Required | Notes |
|---|---|---|---|
| Chat Name or ID | options | Yes | The chat the message lives in. |
| Message ID | string | Yes | Full serialized id of the message. |
| Pin Duration | options | Pin only | 24 Hours (the default), 7 Days, or 30 Days. WhatsApp accepts only these three windows, and there is no way to read a pin back, so a workflow cannot check or refresh one. |
Star takes request body { chatId, messageId, star }.
| Field | Type | Required | Notes |
|---|---|---|---|
| Chat Name or ID | options | Yes | The chat the message lives in. |
| Message ID | string | Yes | Full serialized id of the message. |
| Star | boolean | Yes | On by default; turn it off to remove the star. Always sent, since the server has no default and omitting it is a 400. A star is private to this account and never visible to the other party. |
Vote Poll casts a vote on a poll someone else sent, with request body
{ chatId, pollMessageId, options }. whatsapp-web.js only; Baileys answers 501.
| Field | Type | Required | Notes |
|---|---|---|---|
| Chat Name or ID | options | Yes | The chat the poll lives in. |
| Message ID | string | Yes | The poll message's id. Sent as pollMessageId, not messageId. |
| Selected Options | string list | Yes | The option texts to select, at most 12. Matched by text against the poll's own options, so they must match exactly: a different case or a stray space selects nothing while still reporting success. An option whose own text contains a comma must be supplied as a JSON array, since a comma-separated list would split it. The vote replaces any previous selection, and an empty list clears it, so the key is always sent. |
Send Product sends a product card from this account's catalog, with request body
{ chatId, productId } plus body when a message is set. Baileys only; whatsapp-web.js answers
501, which is the same engine restriction the Catalog resource carries, so the operation that finds
a product id and the one that sends it agree on the engine.
| Field | Type | Required | Notes |
|---|---|---|---|
| Chat Name or ID | options | Yes | Recipient. |
| Product ID | string | Yes | The product's id in this account's catalog; find it with Catalog → List Products. An id that is not in the catalog answers 404. A product with no image cannot be sent as a card, which the server reports as a 400. |
| Message | string | No | Optional text alongside the card. Sent as body. |
Send Image, Send Video, Send Audio, Send Document and Send Sticker all answer 501 on
whatsapp-web.js when the Chat Name or ID is a channel (<id>@newsletter). The same operations
work on either engine for a person or a group. Send Text to a channel works on both.
Contact resource
Check Exists takes a Phone Number, Get Profile Pictures takes a list of ids, List and List
Blocked take only pagination or nothing at all, and every other contact operation takes a Contact
Name or ID (a WhatsApp JID such as 628123456789@c.us).
| Operation | Field | Type | Required | Notes |
|---|---|---|---|---|
| Check Exists | Phone Number | string | Yes | Digits only, for example 628123456789. The node strips +, spaces, -, and (); a value with any non-digit left over fails the item. |
| Get Info | Contact Name or ID | options | Yes | For example 628123456789@c.us. Empty values fail the item. |
| Get Phone | Contact Name or ID | options | Yes | Resolves the contact's phone number. |
| Get Profile Picture | Contact Name or ID | options | Yes | Returns the contact's profile-photo URL. |
| Get Profile Pictures | Contact IDs | string list | Yes | Pictures for many contacts at once, sent as ?ids=. At most 50 — a longer list fails the item rather than being silently truncated by the server. |
| List | Options: Limit, Offset | collection | No | Returns the session's contacts, paginated. |
| List Blocked | (none) | n/a | n/a | The ids this account has blocked. Bare id strings, not contact objects; see the wrapping note under Output and error handling. |
| Save | Contact Name or ID, First Name, Last Name | string | First Name yes | Writes the addressbook entry. Both names are capped at 100 characters. |
| Delete | Contact Name or ID | options | Yes | Removes the entry from the addressbook. The chat and its history are untouched. |
| Block | Contact Name or ID | options | Yes | Blocks the contact. |
| Unblock | Contact Name or ID | options | Yes | Unblocks the contact (DELETE on the block endpoint). |
Check Exists returns whatsappId, the engine-canonical chat id. It can differ from the number
you sent (for example an @lid id), so read the recipient id from the response rather than
reconstructing it.
Save overwrites the whole entry rather than patching it, so a blank Last Name clears any last
name already stored. The node omits the key when it is blank rather than sending null, which the
engine would write as a literal null.
List Blocked returns ids alone on both engines. whatsapp-web.js can resolve full contact models and Baileys cannot, so the server publishes the honest common subset instead of letting the two engines describe the same account differently.
Chat resource
Chat operations act on a conversation rather than the session. Most post the target chat in the
request body; List takes none, and Clear Messages is the one that names the chat in the path
and is a real DELETE.
| Operation | Fields | Notes |
|---|---|---|
| List | Options: Limit (default 50), Offset (default 0) | The session's chats, one item per chat. Each row carries archived, pinned and muted, plus muteExpiration when muted, so the state the write operations below set can be read back. |
| Mark Read | Chat Name or ID, Message IDs | Body { chatId }, plus messageIds when supplied. Leave the ids empty to mark the whole chat read; supply them to ack specific messages, at most 100. |
| Mark Unread | Chat Name or ID | Body { chatId }. |
| Delete | Chat Name or ID | Body { chatId }. A POST, not a DELETE — the server takes the chat id in the body here. |
| Clear Messages | Chat Name or ID | DELETE /api/sessions/{sessionId}/chats/{chatId}/messages. Empties the chat but keeps it in the list. |
| Archive | Chat Name or ID, Archive | Body { chatId, archive }. Set Archive off to unarchive. |
| Pin | Chat Name or ID, Pin | Body { chatId, pin }. WhatsApp allows at most three pinned chats. |
| Mute | Chat Name or ID, Mute Until | Body { chatId, muteUntil }, epoch milliseconds. A seconds-scale value lands in 1970 and the mute expires immediately. |
| Set State | Chat Name or ID, State | Body { chatId, state }. State is Typing, Recording, or Paused (default Typing); Typing and Recording show the indicator in the chat, Paused clears it. |
Group resource
List, Create, Join, and Get Join Info are session-level; every other operation takes a Group Name
or ID (a JID such as 120363021234567890@g.us).
| Operation | Fields | Notes |
|---|---|---|
| List | Options: Limit (1–1000), Offset | Left empty, the server applies its own defaults (limit 1000, offset 0). |
| Create | Group Name, Participants | Name is required and capped at 100 characters; Participants is required and capped at 256. Baileys engine only since server v0.16.0 — whatsapp-web.js, the server default, answers 501 Operation not supported by the active engine: createGroup. See Groups. |
| Join | Invite Code | The part after https://chat.whatsapp.com/. A full invite link is accepted and reduced to the code. Maximum 128 characters. |
| Get Join Info | Invite Code | Previews the group behind a code without joining, so it is safe to run on a code from an untrusted source. Same link tolerance and 128-character cap as Join. There is no participant list, only a count, and only when WhatsApp discloses one; fields the engine does not report are omitted rather than defaulted. |
| Get | Group Name or ID | Group info including participants. |
| Leave | Group Name or ID | Leaves the group. |
| Add / Remove / Promote / Demote Participants | Group Name or ID, Participants | Body { participants }, capped at 256. Remove Participants is a DELETE that carries a JSON body — the API takes the list there rather than in the query string. |
| Update Subject | Group Name or ID, Subject | Required, maximum 100 characters. |
| Update Description | Group Name or ID, Description | Maximum 1024 characters. An empty value is sent as-is and clears the description. |
| Get Settings | Group Name or ID | Reads announce, locked, the member-add mode, and the disappearing-message timer. |
| Update Settings | Group Name or ID, Settings | Partial — settings you leave out stay untouched, and at least one is required. |
| Get Invite Code | Group Name or ID | The invite code and link. |
| Revoke Invite Code | Group Name or ID | Revokes the code and generates a new one. |
| Get Membership Requests | Group Name or ID | The join-approval queue, one item per pending request. Only populated when the group has admin approval enabled, and admin-only on both engines. |
| Approve / Reject Membership Requests | Group Name or ID, Requesters | Body { participants }, capped at 256. Leave Requesters empty to act on the whole queue rather than failing, which is why it is the one participant list the node accepts empty. An empty queue is a no-op returning an empty results list. |
| Get Picture | Group Name or ID | The group picture's URL. |
| Set Picture | Group Name or ID, Picture Source (+ source fields) | The media source model, defaulting to Binary; base64 falls back to image/jpeg. The resolved media fields form the whole request body. Admin-only, and a base64 or binary payload over the server's media cap answers 413. |
| Delete Picture | Group Name or ID | Removes the picture. Admin-only. |
The Settings collection offers:
| Option | Type | Default | Notes |
|---|---|---|---|
| Announce | boolean | off | Only admins can send messages to the group. |
| Locked | boolean | off | Only admins can edit the group info. |
| Member Add Mode | options | All Members | Who may add participants: All Members (all) or Admins Only (admins). |
| Disappearing Messages (Seconds) | number | 604800 | Disappearing-message timer; 0 disables it. Baileys engine only — whatsapp-web.js returns 501. |
Add, Remove, Promote, and Demote report a per-participant outcome in results[], and a partial
refusal does not fail the batch — check results[].success rather than the top-level success.
Approve and Reject Membership Requests report the same way. On whatsapp-web.js the engine pauses
250 to 500 ms between requesters as upstream anti-abuse pacing, so clearing a long queue is a
proportionally long request: raise the node's timeout before running it on a busy group.
Profile resource
Changes the session's own WhatsApp profile.
| Operation | Fields | Notes |
|---|---|---|
| Set Name | Name | Required, maximum 25 characters. |
| Set Status | Status | Maximum 139 characters. An empty value is sent as-is and clears the about text. |
| Set Picture | Picture Source, and the matching Binary Property / Picture URL / Base64 Data + MIME Type | The media source model; base64 defaults to image/jpeg. The resolved media fields form the whole request body. |
| Delete Picture | (none) | Removes the account's own picture. |
Label resource
WhatsApp Business labels — the catalogue on the account, and the labels attached to one chat.
| Operation | Fields | Notes |
|---|---|---|
| List | (none) | whatsapp-web.js only; Baileys answers 501. Every label on the account. |
| Get | Label Name or ID | whatsapp-web.js only; Baileys answers 501. One label. |
| Create or Update | Label ID, Fields: Name, Color | Baileys only; whatsapp-web.js answers 501. A PUT, because the id is caller-chosen: an unused id creates, an existing one rewrites that label rather than failing. At least one field is required, and the write replaces the whole label: a field left out is not preserved, so send every field the label should keep. |
| Delete | Label ID | Baileys only; whatsapp-web.js answers 501. The label disappears from every chat it was on. |
| Get Chats | Label Name or ID | whatsapp-web.js only; Baileys answers 501. |
| Get for Chat | Chat Name or ID | whatsapp-web.js only; Baileys answers 501. The labels attached to a chat. |
| Add to Chat | Chat Name or ID, Label Name or ID | Body { labelId }. |
| Remove From Chat | Chat Name or ID, Label Name or ID | Both ids go in the path. |
The whole resource is split down the middle, and no session gets both halves. Baileys carries the label writes and cannot query labels at all; whatsapp-web.js carries every read and cannot edit a label. Only Add to Chat and Remove From Chat work on both, since those write the chat rather than the label.
That split is also why Create or Update and Delete take a plain-text Label ID rather than the dropdown the other operations use: the picker is fed by a read the engine serving those two writes cannot perform, so a picker would always be empty there.
The Fields collection on Create or Update:
| Field | Type | Notes |
|---|---|---|
| Name | string | Label text, up to 100 characters. A blank value fails the item rather than being dropped: the server marks the field non-empty, so a blank cannot mean "clear the name". Leaving the field out is not a safe no-op either: the write replaces the whole label, so a name that is not sent is lost. |
| Color | number | An index into WhatsApp's twenty predefined colours, 0 to 19, not a hex value. 0 is a real colour, so the field lives in a collection to keep "not set" distinguishable. A read returns a hex string that cannot be converted back, so read-then-write does not round-trip the colour. |
Status resource
Reads the Status (Stories) feed and posts new updates.
| Operation | Fields | Notes |
|---|---|---|
| List | (none) | The status feed. |
| Get by Contact | Contact Name or ID | One contact's status updates. |
| Get Media | Status ID, Put Output in Field | Streams the stored image or video bytes. The response is attached as a binary property named by Put Output in Field (default data), not as JSON. |
| Delete | Status ID | Deletes one of your own status updates. |
| Send Text | Text, Background Color, Font, Recipients | Body { text } plus backgroundColor, font, and recipients when set. Text is required, maximum 4096 characters. |
| Send Image | Image Source (+ source fields), Caption, Recipients | The media object nests under image rather than sitting flat on the body. |
| Send Video | Video Source (+ source fields), Caption, Recipients | The media object nests under video. |
| Send Voice | Audio Source (+ source fields), Background Color, Recipients | Posts an audio status as a voice note. The media object nests under audio, and there is no caption. The source dropdown defaults to Binary; base64 falls back to audio/ogg; codecs=opus. |
| Field | Type | Notes |
|---|---|---|
| Background Color | color | Sent as backgroundColor. Leave empty for the server default. |
| Font | options | Font index from the WhatsApp status font set: Default (0), Font 1, Font 2, Bold (6), Font 7, Font 8, Font 9, Font 10, and Server Default (the node's default), which omits the field entirely. |
| Caption | string | Maximum 1024 characters. |
| Recipients | string list | Who may see this status: at most 256 @c.us or @lid ids, never a group. Required on the Baileys engine, which honours it. whatsapp-web.js ignores it entirely and posts to the whole contact list whether a list is supplied or not, logging a warning server-side when one is, so treat it as an audience restriction on Baileys alone. |
Template resource
Reusable message bodies with {{variable}} placeholders, stored per session and rendered by
Message → Send Template.
| Operation | Fields | Notes |
|---|---|---|
| List | (none) | Every template in the session. |
| Create | Name, Body, Header, Footer | Name (max 100) and Body (max 4096) are required; Header and Footer are optional, max 1024 each. |
| Get | Template Name or ID | One template. |
| Update | Template Name or ID, Update Fields | Partial: the collection offers Body, Footer, Header, and Name, with the same limits. At least one is required. |
| Delete | Template Name or ID | Deletes the template. |
A blank value in Update Fields means different things per field, because the server validates them differently. Name and Body are non-empty on the server, so a blank one fails the item with a message naming the field rather than being silently dropped, which would report success while leaving the template untouched. Header and Footer carry no such rule, so a blank there is a deliberate clear and is sent as one. Add a field only to change it; remove it from the collection to leave it alone.
Channel resource
WhatsApp Channels (newsletters) the session follows.
| Operation | Fields | Notes |
|---|---|---|
| List | (none) | whatsapp-web.js only; Baileys answers 501. Followed channels. |
| Get | Channel Name or ID | One channel. Works on both engines. |
| Get Messages | Channel Name or ID, Options: Limit (1 to 100, default 50) | whatsapp-web.js only; Baileys answers 501. A channel's messages. |
| Create | Channel Name, Description | Name is required, maximum 100 characters; Description is optional, maximum 2048. The account becomes the owner, which is what makes Delete possible later. A blank description is omitted rather than sent as an empty one. |
| Subscribe | Invite Code | Baileys only; whatsapp-web.js answers 501, because its library subscribes by channel id and has no invite-code path. The part after https://whatsapp.com/channel/. A full link is accepted and reduced to the code. |
| Unsubscribe | Channel Name or ID | A DELETE on the channel — it unfollows rather than deletes. |
| Delete | Channel Name or ID | Irreversible, and every subscriber loses the channel. Owner only. Deliberately a POST on a separate path so a slip of the wrist cannot turn Unsubscribe into this. |
| Mute | Channel Name or ID, Mute | Silences notifications for this account; the subscription is untouched. The flag defaults to on and is always sent, since the server has no default of its own. Turn it off to unmute. |
| Demote Admin | Channel Name or ID, User ID | Demotes an admin back to a subscriber. Owner only, and Baileys only: whatsapp-web.js answers 501 because the module function its library targets no longer exists. There is no promote counterpart on either engine, so an admin is promoted from the WhatsApp app and demoted here. |
| Transfer Ownership | Channel Name or ID, New Owner ID | Irreversible: once it lands this session is no longer the owner and cannot take the channel back through this API. Owner only, and Baileys only; whatsapp-web.js answers 501. |
Channels are the most engine-split resource here, and the split runs both ways: Baileys is the only engine that can subscribe by invite code, and whatsapp-web.js is the only one that can list channels or read their messages. So neither engine covers the whole resource, and a workflow that subscribes to a channel cannot then list it on the same session. Get, Create, Unsubscribe, Delete and Mute are the operations that work on either.
User ID and New Owner ID accept a bare phone number, which the server qualifies into a JID.
Unsubscribe and Delete are the pair to keep straight: the first leaves a channel someone else runs, the second destroys one this account owns. They differ in both verb and path for that reason.
Call resource
| Operation | Fields | Notes |
|---|---|---|
| Reject | Session Name or ID, Call ID | The call id as delivered by the Trigger's call events. Pair it with call.received to auto-decline. |
| Create Link | Session Name or ID, Call Type, Start Time | Generates a shareable WhatsApp call link anyone can join. Call Type is Video (the default) or Audio. Start Time is a date-time picker the node converts to epoch milliseconds; leave it empty for now, which is what the blank picker means, because the server requires the field. The response carries the link alone with no expiry, so the node cannot report how long it stays valid. |
Observability resource
Server health, for alerting from inside a workflow. None of these are scoped to a session, so the resource has no Session ID and no other fields.
| Operation | Notes |
|---|---|
| Check | GET /api/health — the server's health JSON as-is. |
| Check Liveness | The liveness probe. |
| Check Readiness | The readiness probe. This is the one that also probes the database connections, so it reports a server that is running but not yet able to serve. |
System resource
Server-wide reporting. Settings are read-only here on purpose: the server derives them from its
environment; the PUT /api/settings route was removed in v0.19.0 after always answering 501.
| Operation | Fields | Notes |
|---|---|---|
| Get Settings | (none) | The server settings document. ADMIN key, unscoped. |
| Get Stats Overview | (none) | Overview statistics across the instance. ADMIN key, unscoped. |
| Get Message Stats | Period | Message statistics across the instance. ADMIN key, unscoped. Period is Last 24 Hours (the default), Last 7 Days, or Last 30 Days, and the bucket size follows from it: hourly for 24 hours, daily for the longer windows. The route binds its query to a DTO, so period is the only key it accepts and anything else is refused rather than ignored. |
| Get Session Stats | Session Name or ID | Statistics for one session. Any valid key. |
| Get Audit Log | Filters | The audit log. ADMIN key. Unlike the other ADMIN reads here it accepts a session-scoped key, which simply narrows the result. |
| Search | Query, Filters | Cross-session message search. Needs an OPERATOR key, so a VIEWER key answers 403 on this read; q is the only required parameter. On an instance running a plugin search provider, 503 means the provider did not answer and is worth retrying, 502 means it answered with an unusable shape and a retry will not help, and 501 means no provider is configured. The built-in provider returns neither 502 nor 503. |
The Search filters:
| Filter | Type | Notes |
|---|---|---|
| Chat ID | string | Only search within this chat. Deliberately free text, not a dropdown — Search spans every session, so there is no single session whose chats could be listed. |
| Date From / Date To | dateTime | Bounds on the message time. The UI supplies ISO-8601 and the node converts each to the epoch-milliseconds number the API binds; an unparseable value fails the item. |
| Direction | options | Incoming or Outgoing. |
| From | string | Only return messages from this sender. |
| Limit / Offset | number | Pagination, defaults 50 and 0. |
| Session Name or ID | options | Only search within this session. |
| Type | string | Only return messages of this type, for example text or image. |
The Get Audit Log filters:
| Filter | Type | Notes |
|---|---|---|
| Action | string | Only entries for this action. |
| API Key Name or ID | options | Only entries recorded for this key. Sent to the API as apiKeyId. |
| Limit / Offset | number | Pagination, defaults 50 and 0; Limit is capped at 200 in the UI. |
| Session Name or ID | options | Only entries for this session. |
| Severity | options | One of info (the default), warn or error. A dropdown, not free text. |
API Key resource
API-key administration. Every operation except Validate needs the credential itself to carry the ADMIN role. Create returns the new key's plaintext exactly once — capture it in the same execution, because it cannot be read back afterwards.
| Operation | Fields | Notes |
|---|---|---|
| List | (none) | Every key. |
| Create | Name, Fields | Name is required; the Fields collection carries the optional restrictions below. |
| Get | API Key Name or ID | One key. The id, not the key itself. |
| Update | API Key Name or ID, Fields | Partial — only the fields you set are changed, and an empty list never clears an existing whitelist. At least one field is required. |
| Revoke | API Key Name or ID | Deactivates the key. |
| Delete | API Key Name or ID | Removes the key. |
| Validate | (none) | Validates the credential this node is already authenticating with. Works with any valid key. |
The Fields collection offers:
| Option | Type | Default | Notes |
|---|---|---|---|
| Name | string | — | A friendly name for the key. |
| Role | options | Viewer | Admin, Operator, or Viewer. |
| Expires At | dateTime | — | When the key stops working. The node normalises whatever the field resolves to into an ISO-8601 string, and refuses a date already in the past or more than a hundred years out. The server accepts any ISO date and checks neither, so this catches a mistake the API would answer 200 to. |
| Allowed IPs | string list | — | Restrict the key to these addresses. Single IPs and CIDR ranges are both accepted. |
| Allowed Sessions | string list | — | Restrict the key to these sessions, by id. A session name matches nothing, so it silently scopes the key to no session at all. |
Webhook resource
Use these to register, update, or inspect a webhook from a workflow. For an event-driven workflow, prefer the Trigger node, which registers and removes its own webhook automatically.
Create — request body { url, events }, plus secret when set.
| Field | Type | Required | Notes |
|---|---|---|---|
| Webhook URL | string | Yes | Destination that receives event deliveries. |
| Events | multiOptions | Yes | One or more events. At least one must be selected. |
| Webhook Secret | string (password) | No | If set, OpenWA signs each delivery with X-OpenWA-Signature (HMAC-SHA256); 16+ characters required since v0.20.0. Sent as secret. |
Update — partial update (PUT): only the fields you set in Update Fields are changed;
everything else keeps its current value.
| Field | Type | Required | Notes |
|---|---|---|---|
| Webhook Name or ID | options | Yes | The webhook to update. The dropdown lists a session's webhooks by delivery URL. |
| Update Fields | collection | No | The changes to apply — see the options below. |
The Update Fields collection offers:
| Option | Type | Notes |
|---|---|---|
| URL | string | New delivery URL. |
| Events | multiOptions | Replaces the full set of subscribed events (not merged). When set, at least one event must be selected. |
| Active | boolean | Whether the webhook is enabled. |
| Retry Count | number | Maximum delivery attempts, 0–5. |
| Secret | string (password) | A non-empty value rotates the HMAC-SHA256 signing secret; an empty value is ignored, so a blank field cannot accidentally unsign a webhook. |
| Clear Secret | boolean | Stops signing. It sends secret: "", which the server reads as "stop signing", so disabling signature verification no longer needs a recreate or a hand-built request. |
| Headers (JSON) | json | Custom delivery headers as a flat JSON object of string values, for example {"X-Team":"ops"}. Headers are non-nullable server-side — clear them by sending {}. |
| Filters (JSON) | json | Advanced delivery filters as a JSON object, for example {"conditions":[...]}. Enter null to clear existing filters. |
Get / List / Delete / Test — read, remove, or probe a webhook.
| Operation | Fields | Notes |
|---|---|---|
| Get | Session Name or ID, Webhook Name or ID | One webhook. |
| List | Session Name or ID | The session's webhooks. |
| Delete | Session Name or ID, Webhook Name or ID | Removes it. |
| Test | Session Name or ID, Webhook Name or ID | Sends a test delivery. |
List All and Get Delivery Failures span every session, so neither shows a Session ID field.
| Operation | Options | Notes |
|---|---|---|
| List All | Limit, Offset | Webhooks across all sessions. GET /api/webhooks binds only limit and offset, so no session filter is offered — one there would be accepted by the UI and then ignored by the server. |
| Get Delivery Failures | Limit, Offset, Session Name or ID | Failed deliveries; this route is the one that does accept a session filter. ADMIN key. |
Automation Rule resource
Server-side auto-reply rules. Every operation takes Session Name or ID; Get, Update and Delete also take a Rule ID, the value Create or List returned.
| Operation | Fields | Notes |
|---|---|---|
| List | (none beyond the session) | The session's rules, one item per rule. |
| Get | Rule ID | One rule. |
| Create | Name, Reply Text, Conditions, Additional Fields | Name is capped at 100 characters and Reply Text at 4096. |
| Update | Rule ID, Update Fields | Only the fields you add are sent; anything left out keeps its stored value. |
| Delete | Rule ID | Removes it. |
Conditions is JSON in the same shape as a webhook filter,
{"conditions":[{"field":"isGroup","operator":"is","value":false}]}. Every condition must match.
Leave it empty and the rule answers every inbound message.
Additional Fields on Create, and Update Fields on Update, both carry Cooldown (Seconds)
(default 60, how long the rule stays quiet in a chat after replying there) and Enabled (default
true). Update additionally accepts Name, Reply Text and Conditions.
A matching message is answered even when the workflow is not running, which makes them the right tool for an out-of-hours acknowledgement and the wrong one for anything needing workflow logic. Conditions use the same field set as webhook filters, so they only ever see message events.
Catalog resource
Reads a WhatsApp Business product catalog. Every operation takes Session Name or ID.
| Operation | Fields | Notes |
|---|---|---|
| List Products | Options (Limit, Page) | Products, paginated. Limit defaults to 50 and Page to 1. |
| Get Product | Product ID | One product. |
| Get | (none beyond the session) | The catalog's own metadata. |
503 here can be permanentwhatsapp-web.js answers 501 on every catalog operation. Use List Products to find the product
id that Message → Send Product needs. A 503 can mean WhatsApp left the catalog query
unanswered, which it does permanently for some business accounts, so treat a repeated 503 as that
account being unsupported rather than as a transient worth retrying.
Media resource
Server-side conversion into the formats WhatsApp actually plays. Every operation takes Session Name or ID.
| Operation | Fields | Notes |
|---|---|---|
| Check Availability | (none beyond the session) | Whether conversion is configured and ffmpeg is runnable. |
| Convert to Voice Note | Media Source, and the field that source needs | Produces Ogg/Opus. |
| Convert to Video | Media Source, and the field that source needs | Produces a compatible MP4. |
Media Source selects one of Binary (default; takes Input Binary Field, default data),
URL (takes Media URL), or Base64 (takes Base64 Data). No MIME type is sent, because the
server reads it from the bytes.
Conversion returns base64 and mimetype ready to feed straight into a send. Convert to Voice
Note produces the Ogg/Opus that Message → Send Audio (Base64 source, Send as Voice Note on) or
Status → Send Voice needs; Convert to Video produces the MP4 for Message → Send Video.
Nothing else in the pipeline transcodes, so without this an MP3 sent as a voice note produces a
microphone bubble that will not play. Conversion is opt-in on the server and needs ffmpeg: run
Check Availability first, and read a 503 as conversion being disabled or busy rather than as a
bad request.
Presence resource
Presence subscription, per-chat reads, and the account's own availability. Every operation takes Session Name or ID.
| Operation | Fields | Notes |
|---|---|---|
| Subscribe | Chat Name or ID | Subscribes to presence updates for that chat, so presence.update events start arriving. |
| Get | Chat Name or ID | The last reported presence for that chat. Answers an empty object when nothing has been reported yet. |
| Set Own Presence | Available | Whether the account announces itself as online. |
A subscription and the account's own availability both live on the socket, so a restart, a
Stop/Start, or any automatic reconnect ends them, and nothing on the server re-issues them. Re-run
these from a Trigger branch on session.status reaching ready, not once at workflow start.
Subscribe is Baileys only; whatsapp-web.js answers 501 and never reports presence at all.
Set Own Presence has a side effect worth knowing: WhatsApp routes notifications away from the phone while a linked device is online, so a workflow-driven session left available suppresses the account holder's own alerts. Turn it off to hand them back. There is no read-back, and the setting resets on every reconnect.
Output and error handling
On success, each input item produces one output item containing the OpenWA JSON response, with two
exceptions. A route answering a bare JSON array emits one item per row on node version 2, each
carrying pairedItem back to the input item that produced it; on node version 1 that array stays
whole on the single item. And the two media reads, Message → Get Media and Status → Get
Media, return raw bytes, which the node attaches as a binary property rather than putting on
json, leaving that item's json empty. A DELETE that answers 204 No Content surfaces as
{ "success": true } so downstream nodes receive a readable item instead of an empty one.
The action node declares versions 1 and 2. Version 2, which a newly added node gets, emits one
item per row for a route answering a bare JSON array. Version 1 keeps the whole array on a
single item's json, exactly as 0.10.0 did. n8n keeps a node's saved version across package
upgrades, so a workflow built before 1.0.0 that reads {{ $json[0].id }} is unaffected by the
upgrade and needs no change.
1.0.0 shipped the per-row output under version 1, so a node added while on 1.0.0 carries
version 1 too and returns to the whole array once the package is upgraded to 1.0.1. Delete and
re-add that node to move it to version 2.
On version 2:
{{ $json[0].id }}becomes{{ $json.id }}, evaluated once per row.{{ $json.length }}becomes{{ $('OpenWA').all().length }}.- A Code node reading
$input.first().jsonas the array reads$input.all().map(item => item.json), or switches to Run Once for Each Item and reads$json. - A Split Out node placed after the operation to fan the array out is redundant, and fails because it has no field left to point at. Remove it.
An empty list emits zero items on version 2, not one holding [], so the next node does not run
at all. Turn on Always Output Data where a branch must continue past an empty result, and drop
any {{ $json.length === 0 }} check, which no longer has an item to run on. With several input items
the output is one flat list: three input items listing ten chats each produce thirty items.
The nineteen affected operations: Session → List All; Chat → List; Contact → List and List Blocked; Group → List and Get Membership Requests; Message → Get History and Get Reactions; Label → List, Get Chats and Get for Chat; Template → List; Channel → List and Get Messages; Webhook → List, List All and Get Delivery Failures; Automation Rule → List; API Key → List.
Contact → List Blocked answers bare id strings, and a string is not valid item json, so on
version 2 each row arrives wrapped: { "data": "628123456789@c.us" }. Version 1 passes the bare
array of strings through untouched.
Operations answering an envelope keep their single item and their nested array on both node
versions, because spreading the rows would lose the envelope's own fields: Message → List
({ messages, total }), System → Search, System → Get Audit Log, Status → List and Get
by Contact, Contact → Get Profile Pictures, and Catalog → List Products, whose pagination
would be lost with it.
The node fails the workflow on the first error unless Continue On Fail is enabled, in which case
the failing item's output is { "error": "<message>" } and processing continues. Validation problems
(empty chat id, non-digit phone number, no event selected, malformed session id, a poll outside 2–12
options, an oversized participant list) surface as node errors carrying the offending item's index;
HTTP failures from OpenWA surface as API errors carrying the server's status and message.
OpenWA Trigger (node)
The trigger node registers a webhook with OpenWA when the workflow activates and starts the workflow each time a subscribed event arrives. It has no input; its single output emits one item per delivery.
Fields
| Field | Type | Required | Notes |
|---|---|---|---|
| Session Name or ID | options | Yes | Session to receive events from, chosen from the same dropdown the action node uses. Sanitized like the action node (no empty, .., /, or \). |
| Events | multiOptions | Yes | Events to subscribe to. Default ['message.received']. At least one is required. |
| Webhook Secret | string (password) | No | Shared secret for signature verification, at least 16 characters. The node checks the length itself before registering, so a short one fails at activation rather than as a server 400. See Signature verification. |
| Filters | json | No | Server-side filters, {"conditions":[{"field":"type","operator":"is","value":["text"]}]}. The gateway drops a non-matching event before delivering, so it never starts an execution. Conditions are ANDed, at most 20. Fields: sender, recipient, body, type, isGroup, kind, fromMe, hasMedia, mentions. kind (server v0.23.4+) is the only way to single out or exclude a Channel post, which isGroup reports as false along with everything else. Filters narrow message events only: session, group and call events arrive regardless. Within the message family an is condition on a field an event does not carry suppresses that event outright, so a sender filter combined with a Message Ack subscription drops every ack. A filtered-out delivery is silent, so an over-strict filter looks exactly like nothing having happened. |
| Deduplicate Deliveries | boolean | No | Default off. Drops a repeated delivery of the same idempotencyKey, remembering the 500 most recent keys in workflow static data. Best-effort: two deliveries arriving at the same moment can both pass, and a retry whose first run failed is also dropped — enable it only when downstream actions are not idempotent and failed runs are rare. |
The delivery path carries the session (…/webhook/openwa-<sessionId>), so a glance at the URL says
which session it serves. What keeps two Triggers apart is the prefix n8n puts in front of that path:
the node's own webhook id, or the workflow id plus the node name when the node has none. That prefix
is per node instance, so even two Triggers on the same session inside one workflow get distinct
delivery URLs. The session segment is there for legibility, not isolation.
Webhook lifecycle
The node manages its own OpenWA webhook across activation and deactivation. It stores the returned webhook id — along with the session it was registered on and a hash of the registration configuration — in workflow static data, and reuses them on later checks.
- On activate, the node compares a hash of the delivery URL, events, secret, session, and filters
against the stored one. A mismatch means the registration is stale: it is deleted from the session
it was registered on and recreated with the current configuration. Otherwise the node confirms the
webhook still exists with
GET /api/sessions/{sessionId}/webhooks/{webhookId}, and creates one withPOST /api/sessions/{sessionId}/webhooksif it does not. A404there means recreate; any other error is inconclusive, so it propagates rather than risking a duplicate registration, which the server would not de-duplicate by URL. - Existing is not the same as delivering, so the node also checks the registration it read back.
The server dispatches only to webhooks with
active: true, and the URL, the event list, and the filters can all be edited out from under the node, from the dashboard, the API, or the action node's own Webhook → Update. Any of those would leave the trigger reporting healthy while nothing reached it, so a drifted registration is deleted and rebuilt exactly like a stale one. Filters are compared ignoring key order, since the server round-trips them through a JSON column. The secret is the one registered field not compared: the server never serializes it back, and the config hash already catches a local change to it. - On deactivate, the node issues
DELETE /api/sessions/{sessionId}/webhooks/{webhookId}. A404is treated as already deleted; any other error propagates so activation fails loudly rather than orphaning a registration that would keep delivering.
Changing the secret, the events, the filters, or the session takes effect on the next activation — deactivate and reactivate the workflow, and the node re-registers automatically. Editing the registration on the server has the same effect in reverse: the node notices on the next activation and rebuilds it from the workflow's own configuration. No manual cleanup on the server is needed.
Events
The Events field offers the same list in both the trigger and the action node's Webhook → Create and
Update operations, mirroring core's own event catalog. Package 1.0.1 offers all twenty-three
events the gateway dispatches.
| Event | Fires when |
|---|---|
message.received | A new message is received. |
message.sent | A message is sent successfully. |
message.ack | A message delivery or read acknowledgement occurs. |
message.failed | A message fails to send. |
message.revoked | A message is deleted for everyone. |
message.edited | A message's text, or a media message's caption, is edited. The payload carries the new text and a hasMedia flag, never replacement media: WhatsApp cannot edit the attachment itself. |
message.reaction | A reaction is added to or removed from a message. |
status.received | A contact's Status (Story) is received. |
session.status | Any session status change. |
session.qr | A new QR code is generated. |
session.authenticated | The session is authenticated. |
session.disconnected | The session loses connection. |
session.reconnect_loop | A session is stuck in a reconnect loop — once per 5 consecutive attempts. |
session.restriction | WhatsApp places or lifts a restriction on the account. |
presence.update | A subscribed chat's presence changes. Baileys only, and silent until the chat is subscribed with POST /api/sessions/{sessionId}/presence/subscribe; whatsapp-web.js answers that route with 501. |
group.join | A participant joins a group the session belongs to. |
group.leave | A participant leaves a group the session belongs to. |
group.update | A group's metadata or participant roster changes. |
group.join_request | Someone asks to join a group the session administers. Fires only when the group has admin approval enabled. |
call.received | An incoming WhatsApp call is detected. Both engines. |
call.accepted | An incoming call is answered. Baileys only. |
call.rejected | An incoming call is declined, including by auto-reject. Baileys only. |
call.missed | An incoming call goes unanswered. Baileys only. |
whatsapp-web.js hooks the call collection's insert and sees no status at all, so it can report the
ring but never how the call ended. A workflow triggered on call.accepted, call.rejected, or
call.missed simply never runs on a whatsapp-web.js session. call.received fires on both engines.
Every event above exists on every server this package supports, so no per-event version floor
discriminates between them any more — the ≥ 0.16.0 package floor covers the whole catalog. See
Compatibility.
group.join_request joined this catalog in package 0.9.2; core has dispatched it since v0.15.0.
On 0.9.1 it is absent, and because the trigger and the Webhook → Create and Update operations
share this one list, no node offers it there. On that version, register the subscription by hand
with n8n's HTTP Request node against POST /api/sessions/{sessionId}/webhooks, pointing it at
an n8n Webhook node's URL.
Output shape
Each delivery is an envelope. The node passes the parsed JSON body straight through, so $json is
the envelope and the event-specific payload is under data.
| Field | Description |
|---|---|
event | The event name, for example message.received. |
timestamp | ISO 8601 time the event was emitted, for example 2024-01-15T10:30:00Z. |
sessionId | Session the event belongs to. |
idempotencyKey | Stable key for the logical event. |
deliveryId | Identifies a single delivery attempt. Re-minted on a crash replay, so it is not a dedup key. |
data | The event payload. Read message fields from here. |
{
"event": "message.received",
"timestamp": "2024-01-15T10:30:00Z",
"sessionId": "default",
"idempotencyKey": "a1b2c3d4-...",
"deliveryId": "e5f6a7b8-...",
"data": {
"id": "3EB0F5A2B4C...",
"chatId": "628123456789@c.us",
"from": "628123456789@c.us",
"body": "Hello!",
"type": "text",
"timestamp": 1705312200
}
}
Reading the output:
- Reference payload fields through
data, for example{{ $json.data.body }}and{{ $json.data.chatId }}. - Read the message identifier from
data.id. Incoming payloads useid, notmessageId. - OpenWA delivers at least once: a failed POST is retried and a crash-stranded delivery is replayed, both under the same
idempotencyKey. De-duplicate on that key if your downstream actions are not idempotent, either with the node's Deduplicate Deliveries toggle or a Remove Duplicates node. Do not key ondeliveryId: a replay carries a new one. - Message
typeis engine-neutral: voice notes arevoice, shared contacts arecontact, and plain chats aretext. - Some payloads carry extra fields under
data:type: "masked"marks a withheld business message, and amessage.revokedevent carriesrevokedId, the id of the revoked message.
If the request body is missing or not an object, the node emits no items for that delivery rather than failing.
Signature verification
When Webhook Secret is set, OpenWA signs each delivery and the node verifies it before running the workflow. The signature scheme:
- OpenWA computes
HMAC-SHA256(secret, rawBody)over the exact bytes it transmits and sends the result asX-OpenWA-Signature: sha256=<hex>. - The node reads the raw request body — it does not re-serialize the parsed JSON, which could reorder keys or change whitespace and reject a valid delivery — then recomputes the expected
sha256=<hex>and compares. - Comparison uses a constant-time check (
timingSafeEqual). A length mismatch is treated as a non-match before the comparison runs. - Verification returns false when either the secret or the signature header is absent. With a secret configured, a delivery whose signature is missing or wrong is rejected with HTTP 401 and produces no workflow run.
- If the running n8n cannot supply the raw body at all, the node logs a warning and rejects the delivery with
401rather than silently re-serializing and dropping valid deliveries at random.
Leave Webhook Secret empty to skip verification and accept unsigned deliveries. For end-to-end webhook setup and the same signature scheme used outside n8n, see Webhooks.
Common errors
| Symptom | Cause | Fix |
|---|---|---|
| Credential test fails on save | API key rejected by GET /api/sessions | Use a valid key from the dashboard; see Authentication. |
Session ID contains invalid characters | Session id has .., /, or \ | Pick the session from the dropdown, or supply its UUID from an expression. |
Chat ID cannot be empty | Message sent with no Chat ID | Provide a chat id like 628123456789@c.us. |
Phone number must contain only digits | Check Exists got non-digit input that survived stripping | Pass digits only, for example 628123456789. |
| Base64 image or document rejected by OpenWA | Missing MIME type on a base64 payload | Set the MIME Type field, or use the Binary Data or URL source. |
400 when creating a webhook | The server does not know one of the selected events | session.restriction, presence.update, call.accepted, call.rejected, and call.missed need OpenWA ≥ 0.14.0; group.join_request needs ≥ 0.15.0. Upgrade the gateway, or deselect the events it does not know. |
404 on one operation while the rest work | The route postdates the server | Session → Get Proxy and Update Proxy need a server on v0.23.4 or newer; every other route the node calls exists from v0.16.0. See Compatibility. |
| A call-outcome trigger never runs | The session runs whatsapp-web.js | call.accepted, call.rejected, and call.missed are Baileys-only. Use call.received, which fires on both engines. |
| Trigger fires once and then goes quiet | The webhook was registered against n8n's test URL, which stops after one delivery | Activate the workflow so the trigger registers its production URL. |
| Trigger subscribed to a group event never runs | The session isn't a member of any group, or no group activity has occurred | Confirm the session has joined a group and that group activity (joins/leaves/roster changes) is happening. group.join, group.leave, and group.update are emitted by both engines. |
401 Unauthorized on every delivery | Signature mismatch between server and node secrets | Use the same Webhook Secret on both sides; reactivate the workflow after changing it. |
403 on an operation that used to work | The credential's role is too low, or its session scope is too narrow | Writes need an OPERATOR key, and so do several reads: Session → Get QR, Template → List and Get, Webhook → Get, List and List All, System → Search, and every Automation Rule operation. The API Key resource (except Validate), System → Get Settings, Get Audit Log, Get Stats Overview and Get Message Stats, and Webhook → Get Delivery Failures need an ADMIN key. Separately, a session-scoped key is refused outright on the API-key operations, on System → Get Settings, Get Stats Overview and Get Message Stats, and on Session → Create and Update Proxy, whatever its role. |
409 on Request Pairing Code | The session has not reached qr_ready yet, or a code was already accepted | Run Session → Get Status until status is qr_ready, then request the code; after a code is accepted, wait for ready. |
502 or 503 on System → Search | A plugin search provider is active and misbehaved | 503 means it did not answer, so wire n8n's retry-on-fail; 502 means it answered with an unusable shape and retrying will not help. The built-in provider returns neither. |
Compatibility
This package targets a self-hosted OpenWA server ≥ 0.16.0, verified against v0.23.4. Two things set that floor independently.
The routes the action node calls set it at v0.16.0, where Call → Create Link arrived. Most of
the rest of the surface landed in v0.14.0 (message pin, star and vote; chat archive and clear;
presence; media conversion; voice statuses; group pictures and join preview; channel administration;
label writes; automation rules). Session config followed in v0.14.5, membership requests and the
blocklist read in v0.15.0, and chat mute and pin in v0.16.0 alongside the call link. Two operations sit above the floor: Session → Get Proxy and
Session → Update Proxy need ≥ 0.23.4, where the per-session proxy became readable and
patchable rather than fixed at creation. Against an older server those specific operations answer
404 and everything else keeps working.
The event catalog is what actually sets 0.15.0. group.join_request does not exist in core
before v0.15.0, and session.restriction, presence.update, call.accepted, call.rejected and
call.missed do not exist before v0.14.0 — a v0.14.x server knows 22 events, not 23 — and the
server validates a webhook registration against its own event list, so a Trigger subscribing to any
of the six is refused at registration rather than degrading quietly. That is a harder failure than a
404 on one operation. Package 0.9.1 and earlier could not select group.join_request at all, so
their effective floor was 0.14.0.
Since the routes now set the higher of the two, 0.16.0 is the supported floor for 1.0.1.
Next steps
- n8n integration guide — install the package and build your first workflow.
- Webhooks — the event payloads and signing scheme behind the Trigger node.
- API Reference — the HTTP endpoints each operation calls.
- Sending messages — payload details for the message operations.