---
title: "n8n WhatsApp Cloud API Webhook Setup | WebhookRelay"
meta:
  "og:description": "Receive WhatsApp Cloud API webhooks in self-hosted n8n: a stable public URL for Meta, the hub.challenge verify-token handshake answered automatically, and messages streaming into your workflow — no public IP or tunnel."
  "og:title": "n8n WhatsApp Cloud API Webhook Setup"
  description: "Receive WhatsApp Cloud API webhooks in self-hosted n8n: a stable public URL for Meta, the hub.challenge verify-token handshake answered automatically, and messages streaming into your workflow — no public IP or tunnel."
---

![Stripes](https://webhookrelay.com/docs/tutorials/n8n/whatsapp-cloud-api-webhook/images/stripes.svg)

Documentation

**Fundamentals**

# **n8n WhatsApp Cloud API Webhook Setup**

Receive WhatsApp Cloud API webhooks in self-hosted n8n: a stable public URL for Meta, the hub.challenge verify-token handshake answered automatically, and messages streaming into your workflow — no public IP or tunnel.

Meta's WhatsApp Cloud API only delivers webhooks to a **public HTTPS URL**, and before it sends anything it runs a **verification handshake** — a GET request your endpoint must answer by echoing the `hub.challenge` query value. That is two problems for self-hosted n8n: your instance usually is not public, and tunnels with rotating URLs force you to re-verify in the Meta dashboard every time the URL changes.

This tutorial solves both with a stable Webhook Relay endpoint: Meta verifies **one permanent URL** (the handshake is answered by a tiny Function, automatically), and your n8n instance receives every message over an **outbound WebSocket** — no public IP, no open ports, nothing to re-verify.

```
Meta ──GET hub.challenge──▶ Webhook Relay (Function answers handshake)
Meta ──POST messages──────▶ Webhook Relay (stable public URL)
                                 │
        n8n ──outbound WebSocket─┘   ← n8n connects out; nothing inbound
```

## [1. Install the Webhook Relay node in n8n](#_1-install-the-webhook-relay-node-in-n8n)

In n8n go to **Settings → Community Nodes → Install** and enter `n8n-nodes-webhookrelay`. Then create an **API key** at [my.webhookrelay.com/tokens](https://my.webhookrelay.com/tokens) and add it as a **Webhook Relay API** credential. (The general setup, with screenshots, is in the [n8n Webhook Trigger tutorial](https://webhookrelay.com/docs/tutorials/n8n/whatsapp-cloud-api-webhook/docs/tutorials/n8n/webhook-trigger/).)

## [2. Create a bucket for WhatsApp](#_2-create-a-bucket-for-whatsapp)

In [my.webhookrelay.com](https://my.webhookrelay.com/buckets) create a bucket named `whatsapp`. Its default input URL — `https://xxx.hooks.webhookrelay.com/...` — is permanent. This is the URL Meta will verify once and deliver to forever.

## [3. Answer the verify-token handshake with a Function](#_3-answer-the-verify-token-handshake-with-a-function)

Meta's verification GET must be answered with the raw `hub.challenge` value. Attach this Function to the bucket's **input** so the handshake is handled at the edge (n8n never needs to see it):

```
// Answer Meta's one-time subscription verification; let real events through.
var VERIFY_TOKEN = "my-verify-token"; // must match what you enter in Meta

if (r.RequestMethod() === "GET") {
  if (r.RequestQuery("hub.verify_token") === VERIFY_TOKEN) {
    r.SetResponseStatusCode(200);
    r.SetResponseBody(r.RequestQuery("hub.challenge"));
  } else {
    r.SetResponseStatusCode(403);
    r.SetResponseBody("verification failed");
  }
}
```

POST deliveries (the actual messages) pass through untouched and get the bucket's normal `200` response, which is all Meta needs.

## [4. Point Meta at the bucket](#_4-point-meta-at-the-bucket)

In the [Meta App Dashboard](https://developers.facebook.com/apps/): **WhatsApp → Configuration → Webhook → Edit**. Enter the bucket's input URL as the **Callback URL** and the same verify token as in the Function, then save — verification succeeds instantly. Click **Manage** and subscribe to the **`messages`** webhook field.

## [5. Receive messages in n8n](#_5-receive-messages-in-n8n)

Add the **Webhook Relay Trigger** node to a workflow, select the `whatsapp` bucket, and activate. The node opens an outbound WebSocket and every WhatsApp delivery arrives as a workflow execution.

Messages sit a few levels deep in Meta's envelope. A **Set** or **Code** node right after the trigger tidies that up:

```
// Code node: flatten the Cloud API envelope into one item per message
const out = [];
for (const entry of $json.body.entry || []) {
  for (const change of entry.changes || []) {
    for (const msg of change.value?.messages || []) {
      out.push({ json: {
        from: msg.from,
        type: msg.type,
        text: msg.text?.body,
        timestamp: msg.timestamp,
        contact: change.value.contacts?.[0]?.profile?.name,
      }});
    }
  }
}
return out;
```

Status updates (`sent`/`delivered`/`read`) arrive on the same bucket with a `statuses` array instead of `messages` — filter on what is present.

## [Notes](#notes)

- **Restarts are free.** Meta verified the bucket URL, not your n8n instance — redeploy or move n8n anytime, nothing to re-verify.
- **Verify signatures if you need authenticity**: Meta signs POSTs with `X-Hub-Signature-256` (HMAC-SHA256 of the raw body with your app secret). The header reaches your workflow intact, so you can check it in a Code node — see [Verify a webhook signature](https://webhookrelay.com/docs/tutorials/n8n/whatsapp-cloud-api-webhook/blog/verify-webhook-signature/).
- **Payload reference**: the full setup-and-payload story (including self-hosted gateways like WAHA) is in [WhatsApp Cloud API webhooks: setup, verify token and payloads](https://webhookrelay.com/docs/tutorials/n8n/whatsapp-cloud-api-webhook/blog/whatsapp-cloud-api-webhooks/).

Did this page help you?