Meta WhatsApp Webhook Verification failing on n8n Cloud (GET hub.challenge response issue)

Describe the problem/error/question:
I tried to reply to a closed topic discussing the exact same issue, but since it is locked, I am creating a new thread.
I am unable to verify my Meta WhatsApp Cloud API Webhook URL in n8n Cloud. Meta keeps failing to validate the Callback URL.

What is the error message (if any)?
Meta Developer Console displays a general callback verification failure error when clicking “Verify and Save”.

Please share your workflow:

  • Webhook trigger (GET/POST)
  • If node checks: hub.mode == subscribe AND hub.verify_token == n8n_property_bot_verify_token
  • Respond to Webhook node: Returns raw hub.challenge as plain text (HTTP Status 200).

Share the output returned by the last node:
Raw text containing the hub.challenge value with HTTP 200 header.

Information on your n8n setup:

  • n8n version: Latest Cloud
  • Database: Cloud Default
  • Running n8n via: n8n cloud (aboanada67.app.n8n.cloud)
  • Operating system: Cloud Hosted

Hi @Abo_Anad_Abo_Anad Welcome!
Meta sends the verification as a GET and the message deliveries as POST, and the Webhook node registers one method at a time, so a node sitting on POST returns 404 to the verification request and Meta shows only its generic callback failure. Open the node Settings and turn on Allow Multiple HTTP Methods, then keep GET and POST in the HTTP Methods field. The node then has one output per method, so run the GET output into your If node and the Respond to Webhook node, and use the POST output for the message handling.
Click Verify and save with the workflow published and the production URL pasted in Meta, the test URL only listens for 120 seconds.

Hi @Abo_Anad_Abo_Anad

The failure is almost certainly caused by one of three things: using the Test URL instead of the Production URL, an incorrect JSON path in your expression (query parameters are nested), or the response being sent as application/json instead of text/plain.

When you click “Verify and Save” in the Meta Developer Console, Meta sends a real-time GET request to your URL.

  • Do NOT use the Test URL (the one ending in /test). The Test URL only works when you have the n8n editor open and you have manually clicked “Execute Workflow”. Meta’s request will hit a closed listener and fail.
  • DO use the Production URL (the one without /test).
  • Note: You must Publish the workflow for the Production URL to be live.

To pass Meta’s handshake, your nodes must be configured exactly like this:

  • HTTP Method: GET (Note: For actual message receiving, you will later need to add POST, so set this to GET and POST).
  • Path: (Your chosen path, e.g., whatsapp-webhook)
  • Respond: Using 'Respond to Webhook' node (This is mandatory).

Meta sends parameters in the Query String, not the Body. Your “If” node must check the query object.

  • Condition 1 (String): {{ $json.query['hub.mode'] }} Equal subscribe
  • Condition 2 (String): {{ $json.query['hub.verify_token'] }} Equal your_token_here

This is where most users fail. Meta expects the raw string, not a JSON object.

  • Respond With: Text
  • Response Body: {{ $json.query['hub.challenge'] }} (Use an expression)
  • HTTP Status Code: 200
  • Response Headers (Optional but recommended):
    • Name: Content-Type
    • Value: text/plain

When Meta hits your URL, the data structure looks like this:

{
  "query": {
    "hub.mode": "subscribe",
    "hub.verify_token": "n8n_property_bot_verify_token",
    "hub.challenge": "123456789"
  }
}

Your workflow must extract 123456789 and return it as a plain text string, not {"challenge": "123456789"}.

Does this help?

Continuing the discussion from Meta Whats-app Web hook Verification failing on n8n Cloud (GET hub.challenge response issue):

This is almost always one of two things, and both are easy to miss.

First, the response format. Meta’s verification call is a GET with
three query params: hub.mode, hub.verify_token and hub.challenge.
It expects hub.challenge back as raw plain text with a 200. n8n’s
default web hook response is JSON, so Meta receives something like
{“challenge”:“123”} instead of just 123, and rejects it.

Fix:

  1. On the Web hook node, set Respond to “Using Respond to Web hook
    node”
  2. Add a Respond to Web hook node
  3. Set Respond With = Text
  4. Response Body = {{ $json.query[‘hub.challenge’] }}

Text, not J SON. That single setting is usually the whole problem.

Second, and this catches a lot of people on Cloud: make sure you
gave Meta the production URL, not the test URL, and that the
workflow is actually active. The test URL only listens for one call
after you click “Listen for test event” and goes dead after that,
so verification fails silently even though everything looks right
in the editor.

One more thing that will save you later: Meta uses GET for
verification and POST for actual incoming messages, on the same
URL. If you don’t split those, your message handling logic will
run on the verification call too. Add a Switch or IF right after
the web hook checking {{ $json.headers[‘x-forwarded-method’] }} or
the presence of $json.query[‘hub.mode’], and route the two paths
separately.

Worth verifying hub.verify_token matches what you set in the Meta
dashboard before responding, rather than echoing the challenge
back unconditionally.