Prerequisites
- An active Dualhook connection with Webhook Override enabled
- An n8n Cloud or self-hosted instance with a workflow you can publish
- Your Meta webhook verify token (for the GET handshake — set during connection setup in Dualhook)
- A WhatsApp access token and
PHONE_NUMBER_IDfor outbound Graph API sends, such as sending messages - Your
WABA_IDfor payload-side validation
Keep these values in n8n credentials or environment-backed variables rather than hardcoding them into nodes:
WEBHOOK_VERIFY_TOKEN
WHATSAPP_ACCESS_TOKEN
PHONE_NUMBER_ID
WABA_ID
META_GRAPH_VERSION=v25.0
No
META_APP_SECRET? In the Embedded Signup flowX-Hub-Signature-256is signed by Dualhook's Meta app and isn't customer-verifiable — validate inbound webhook payload shape instead. See Messaging Webhook → Inbound Webhook POST Validation.
Architecture Overview
With Dualhook, Meta sends message-path webhooks directly to your n8n webhook URL. Dualhook stays out of the message content path and focuses on setup, monitoring, and management events.
Meta (messages, statuses, errors)
→ n8n Webhook URL
→ verification
→ inbound payload validation
→ business workflow
→ optional Graph API send
Dualhook
→ management events, health visibility, template operations
n8n becomes part of your message-processing boundary. If you keep execution history, pinned data, or verbose logs enabled, message content can be retained in n8n even though Dualhook itself does not store it. See Compliance & Data Retention and Architecture & Security for the privacy split.
Build the Webhook Workflow
Create one production workflow for your Meta callback path.
- Add a Webhook node.
- Set a stable path such as
meta/messages. - If you want GET verification and POST events on the same path, enable Allow Multiple HTTP Methods and accept both
GETandPOST. - Set Respond to Using "Respond to Webhook" Node.
- Add the Raw Body option so n8n keeps the exact bytes Meta signed.
- Add the Binary Property option and set a clear name such as
rawBody. - Publish the workflow and use the Production URL in Dualhook. Do not use the temporary Test URL for real traffic.
The simplest branch layout is:
GETbranch for Meta verificationPOSTbranch for inbound payload validation and event processing
Handle the Verification Handshake
Meta verifies your callback URL with a GET request that includes:
hub.mode=subscribehub.verify_token=<YOUR_VERIFY_TOKEN>hub.challenge=<CHALLENGE_VALUE>
In the GET branch:
- Check that
hub.modeissubscribe. - Compare
hub.verify_tokenwith your expected verify token. - If both match, return the raw
hub.challengevalue with a Respond to Webhook node. - If they do not match, return
403.
Recommended response settings:
- Success:
200, plain text body =hub.challenge - Failure:
403, plain text body =Forbidden
Dualhook uses the same verify token you entered during connection setup, so the value in n8n and the value stored on the Dualhook connection must match.
Validate the Webhook Payload
Validate every POST before you process the payload. Confirm the WhatsApp envelope shape and the WABA ID / phone_number_id against the values you stored for this connection.
Check the envelope, WABA, and phone number
In the POST branch, add an IF (or Code) node that rejects anything that does not look like the expected payload:
object === "whatsapp_business_account"entry[0].id === $env.WABA_IDentry[0].changes[0].field === "messages"entry[0].changes[0].value.metadata.phone_number_idis present and equals$env.PHONE_NUMBER_ID
If any check fails, respond 401 with a Respond to Webhook node and stop the workflow.
Keep the URL unguessable
Use n8n's randomly generated webhook path. Treat that path as a secret: do not paste it into shared chat, do not commit it to repos, and rotate it (publish the workflow to a new path) if it leaks.
Respond early
For valid payloads:
- Return
200as soon as the envelope check passes. - Continue the heavy work after the response step.
For payloads that fail validation:
- Return
401 - Stop processing
This keeps webhook acknowledgments fast enough for Meta retries to stay rare.
Send Template Messages via Graph API
n8n sends outbound messages directly to Meta. Dualhook configures inbound routing and operational visibility, but it is not in the send path.
Use an HTTP Request node with a Bearer-auth credential:
- Method:
POST - URL:
https://graph.facebook.com/v25.0/<PHONE_NUMBER_ID>/messages - Authentication: Bearer auth
- Header:
Content-Type: application/json
Example template send:
{
"messaging_product": "whatsapp",
"to": "12015550123",
"type": "template",
"template": {
"name": "order_update_v1",
"language": { "code": "en_US" }
}
}
For variables, media headers, and button parameters, see Sending Template Messages.
Test with Dualhook
- Publish the n8n workflow so the Production URL is active.
- Put that Production URL in Dualhook as the webhook URL.
- Use the same verify token in Dualhook and in your n8n verification branch.
- Run Dualhook Test Ping to confirm reachability.
- Send a real WhatsApp message to your number.
- Confirm that:
- verification succeeds
- inbound payload validation accepts the real Meta POST and rejects test pings with the wrong shape
- the workflow returns
200quickly - both inbound messages and
statusesevents are handled
Production Caveats
- n8n execution history can store message content. Review retention, logging, and pinning settings if you want to preserve Dualhook's minimal-storage posture downstream.
- Use n8n credentials instead of pasting secrets into node fields. In self-hosted production, set
N8N_ENCRYPTION_KEY. If you run n8n Enterprise, external secrets are worth considering. - Use idempotency in downstream processing. A practical pattern is deduping inbound messages on
wamidand statuses on(message_id, status). - Treat WhatsApp webhook payloads as evolving. With Meta's BSUID rollout and later username launch, some fields are conditional. Parse defensively and prefer field presence checks over rigid assumptions. See the BSUID webhook changes reference.
- If you deactivate or republish the workflow in a way that changes availability, Meta deliveries will fail until the Production URL is healthy again.