WWaSphere Docs
Guides

n8n Integration

Send and receive WhatsApp messages from n8n workflows using WaSphere's REST API and signed webhooks.

n8n Integration

WaSphere is a plain REST API, so it drops straight into n8n with the built-in HTTP Request and Webhook nodes — no custom node required. This guide covers both directions:

  • Sending — fire a WhatsApp message from any n8n workflow
  • Receiving — trigger an n8n workflow when a WhatsApp message arrives (with signature verification)

Prerequisites

  • WaSphere running with a connected session
  • An API key with the messages:send permission (Bearer key, looks like wsk_…)
  • Your Workspace ID (from Settings) and Session ID (the name you gave the session, e.g. support)

All requests go through the Dashboard API, which proxies to your WA Server and injects the server token for you. You only ever send your wsk_ key — never the WA Server token.

Sending a message from n8n

Add an HTTP Request node and configure it like this:

FieldValue
MethodPOST
URLhttps://api.yourdomain.com/workspaces/{workspaceId}/proxy/api/sessions/{sessionId}/messages/text
AuthenticationGeneric → Header Auth
Header nameAuthorization
Header valueBearer wsk_your_key
Body Content TypeJSON

Replace {workspaceId} and {sessionId} in the URL with your real values. JSON body:

{
  "to": "447700900123",
  "text": "Hello {{ $json.name }} — your order is confirmed!"
}

The to field is the recipient's number in international format without the + (e.g. 447700900123). You can use n8n expressions like {{ $json.phone }} for both to and text.

The session ID goes in the URL, not the body. Other message types use the same pattern — swap /messages/text for /messages/image, /messages/document, /messages/buttons, etc. See Send Messages for every type's payload.

Raw cURL equivalent

curl -X POST \
  https://api.yourdomain.com/workspaces/{workspaceId}/proxy/api/sessions/{sessionId}/messages/text \
  -H "Authorization: Bearer wsk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "to": "447700900123", "text": "Hello from n8n!" }'

Receiving messages in n8n

1. Create the trigger

Add a Webhook node, set the method to POST, and copy its Production URL (e.g. https://n8n.yourdomain.com/webhook/wasphere).

2. Register the webhook in WaSphere

In the dashboard go to Webhooks → New Webhook (or POST /workspaces/{workspaceId}/webhooks) and set:

  • URL — your n8n Webhook node's Production URL
  • Events — e.g. message.received (also available: message.receipt, session.connected, session.disconnected, bulk.sent, and more)
  • Signing secret — generate one with openssl rand -hex 32 and save it; you'll need it to verify deliveries

3. Payload shape

Each delivery is a JSON body:

{
  "event": "message.received",
  "sessionId": "support",
  "timestamp": "2026-06-03T10:00:00.000Z",
  "data": {
    "from": "447700900123",
    "text": "Hi, is anyone there?",
    "messageId": "3EB0C767D097B7C7A5C1"
  }
}

4. Verify the signature (important)

Every delivery carries two headers:

  • X-WaSphere-Signaturev1,sha256=<hex>
  • X-WaSphere-Timestamp — the unix timestamp used in the signature

The signature is HMAC-SHA256 of the string `{timestamp}.{rawBody}` using your signing secret. Verify it in a Code node placed right after the Webhook node so you reject forged requests:

const crypto = require('crypto');

const SECRET = 'your-signing-secret'; // same value you set in WaSphere

const sigHeader = $input.first().json.headers['x-wasphere-signature'] || '';
const timestamp = $input.first().json.headers['x-wasphere-timestamp'] || '';
const rawBody   = $input.first().json.body; // raw string — see note below

const expected = 'v1,sha256=' +
  crypto.createHmac('sha256', SECRET).update(`${timestamp}.${rawBody}`).digest('hex');

const ok = sigHeader.length === expected.length &&
  crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(expected));

if (!ok) {
  throw new Error('Invalid WaSphere signature — rejecting delivery');
}

return $input.all();

The signature is computed over the raw request body. In the n8n Webhook node, set Response Mode appropriately and enable Raw Body (Webhook node → Options → Raw Body) so {timestamp}.{rawBody} matches byte-for-byte. If you verify against a re-serialised JSON object, the bytes won't match and verification will fail.

Example: WhatsApp auto-reply

A complete two-node loop:

  1. Webhook (POST) → receives message.received
  2. Code → verify signature (above)
  3. IF → only continue when {{ $json.body.data.text }} contains a keyword
  4. HTTP Request (POST) → reply via the send endpoint above, with to = {{ $json.body.data.from }} and your reply text

That's a working WhatsApp bot in four nodes — no code beyond the signature check.

Troubleshooting

401 / 403 on send — check the Authorization: Bearer wsk_… header is present and the key has the messages:send scope.

Webhook never fires — confirm the webhook is active in the dashboard and the URL is the n8n Production URL (not the Test URL, which only works while the editor is open).

Signature always fails — you're almost certainly verifying against a parsed/re-serialised body. Use the raw body and the exact `{timestamp}.{rawBody}` string.

Next steps

  • Send Messages — every message type and its payload
  • Webhooks — full event list and signature details
  • API Keys — scopes and per-session keys

On this page