Integration

WATI Pro × n8n

Receive every incoming WhatsApp message in n8n via your per-tenant signed webhook, then send replies — text or media — through the WATI Pro REST API.

1

Get your tenant webhook secret + API key

In WATI Pro open Settings → Webhooks & API and copy:

  • Incoming webhook secret — used to verify HMAC signatures from WATI.
  • API key (starts with wati_) — Bearer token for outbound REST calls.

In n8n, save both as environment variables: WATI_WEBHOOK_SECRET and WATI_API_KEY.

2

Create the n8n Webhook node

Add a Webhook node (Method: POST, Path: wati-incoming, Response: "Using 'Respond to Webhook' node"). Enable raw body in node options so the HMAC matches byte-for-byte.

Copy the n8n production URL and paste it into WATI Pro → Settings → Incoming webhook URL. Must be HTTPS.

Sample payload WATI Pro POSTs to your webhook:

{
  "from": "+15551234567",
  "text": "PRICE",
  "messageId": "true_15551234567@s.whatsapp.net_3EB0...",
  "deviceId": "8f3c…-uuid",
  "receivedAt": "2026-06-28T11:32:01.245Z"
}

Signature header: x-webhook-signature: sha256=<hex> over the raw request body using HMAC-SHA256 with your secret.

3

Verify the HMAC signature (Function node)

Reject any request whose signature doesn't match — anyone with the URL could otherwise spoof messages.

// n8n Function node — verify x-webhook-signature
const crypto = require('crypto');
const secret = $env.WATI_WEBHOOK_SECRET;
const sigHeader = $json.headers['x-webhook-signature'] || '';
const raw = JSON.stringify($json.body); // or use the rawBody buffer
const expected = crypto.createHmac('sha256', secret).update(raw).digest('hex');
const got = sigHeader.replace(/^sha256=/, '');
const ok = got.length === expected.length &&
  crypto.timingSafeEqual(Buffer.from(got,'hex'), Buffer.from(expected,'hex'));
if (!ok) throw new Error('Invalid WATI webhook signature');
return [{ json: $json.body }];
4

Reply with text — POST /api/public/v1/send

Use the HTTP Request node. Phone number in E.164 (with country code). The response includes the messageId.

curl -X POST https://wati.pro/api/public/v1/send \
  -H "Authorization: Bearer $WATI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+15551234567",
    "message": "Hello from n8n 👋"
  }'
5

Reply with media — POST /api/public/v1/send-media

Supported type: image, video, audio, document. The url must be a public HTTPS address (private/loopback URLs are blocked by SSRF guard).

curl -X POST https://wati.pro/api/public/v1/send-media \
  -H "Authorization: Bearer $WATI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+15551234567",
    "type": "document",
    "url": "https://example.com/invoice.pdf",
    "fileName": "invoice.pdf",
    "caption": "Your invoice 📄"
  }'
6

Test the signed webhook locally (ngrok)

Before pointing production traffic at n8n, dry-run the whole signature flow from your laptop. ngrok gives the Webhook node a public HTTPS URL that WATI Pro (and curl) can reach.

# 1. Start n8n locally (default port 5678)
n8n start

# 2. In another terminal, expose it over HTTPS
ngrok http 5678
# → Forwarding  https://abc123.ngrok-free.app -> http://localhost:5678

# 3. Paste the HTTPS URL + your webhook path into
#    WATI Pro → Settings → Incoming webhook URL:
#    https://abc123.ngrok-free.app/webhook/wati-incoming

Send a fake signed event and confirm Steps 2–3 accept it:

# Compute the signature for the sample body with your secret
BODY='{"from":"+15551234567","text":"ping","messageId":"test_1","deviceId":"local","receivedAt":"2026-06-28T11:32:01.245Z"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$WATI_WEBHOOK_SECRET" -hex | awk '{print $2}')

curl -i -X POST https://abc123.ngrok-free.app/webhook/wati-incoming \
  -H "Content-Type: application/json" \
  -H "x-webhook-signature: sha256=$SIG" \
  -d "$BODY"

# Expected: HTTP/1.1 200 OK  (from your "Respond to Webhook" node)
# Bad secret / tampered body → Function node throws and n8n returns 500.

If you see 200 OK the HMAC verify node is wired correctly. Flip one character in $BODY and resend — n8n should reject it with 500 Invalid WATI webhook signature.

7

Import the ready-made template

In n8n: Workflows → Import from File and pick the JSON below. It contains: Webhook → HMAC verify → IF branch (greeting vs media) → send / send-media → Respond 200.

wati-pro-workflow.json

Errors you'll hit (and what they mean)

  • 401 Invalid Bearer API key — token missing/revoked or wrong workspace.
  • 402 Plan limit reached — monthly message quota exhausted; upgrade or wait for renewal.
  • 409 Device is not connected — re-scan the WhatsApp device or pass a different deviceId.
  • 400 URL must be a public https:// address — send-media URL is private or non-HTTPS.
  • Invalid signature in n8n — secret mismatch or you're hashing the parsed JSON instead of the raw body.