A webhook address is like a door. Anyone who knows the address can send a request to it. If you don't verify the address Meta sends message events to, then when someone sends a fake event saying "customer placed an order" or "payment confirmed", your flow treats it as real. Meta attaches a signature to every request for exactly this reason. In this article we explain what that signature is and how to check it in n8n. We have hardly come across this topic in Turkish sources; they generally only explain the verification step during initial setup and skip the signature side.
Short Answer
Meta adds an X-Hub-Signature-256 header to every POST request. This is the HMAC-SHA256 value of the request's raw body, computed with your app secret. You perform the same calculation in n8n and compare it with the value in the header; if they don't match, you drop the request without processing it. The most common mistake is trying to compute the hash over the parsed JSON.
There Are Two Separate Verifications
The two are often confused. The first happens when you first define the webhook: Meta sends a GET request to your address containing hub.mode=subscribe, hub.verify_token and hub.challenge. verify_token is a string you choose. If the incoming value matches the one you stored, you return the hub.challenge value with a 200 status code. According to Meta's documentation, any other response, including 400-series codes, leaves the address unverified and no webhooks arrive.
The second is the signature check that runs continuously, and it provides the real security. verify_token is asked only once, at setup time. After that, it is the signature that verifies the source of incoming message events.
How Is the Signature Created?
The format Meta describes is this: the value of the X-Hub-Signature-256 header starts with "sha256=", followed by a hash. This hash is an HMAC-SHA256 computed using the request body and your app's secret key. What you need to do is perform the same calculation with the incoming body and your own secret key, and compare the result with the part after "sha256=" in the header. If they match, the payload is valid, in the words of Meta's documentation, and you return 200; if they don't match, you return a code other than 200.
The secret key is the app secret value found in your app's settings in the Meta for Developers dashboard. Instead of writing it as plain text inside the workflow, it is better to keep it in n8n's credential system or in an environment variable.
Why Is the Raw Body Essential?
HMAC depends on the exact bytes of the body. A difference of a single space or escape character produces a completely different hash. The n8n Webhook node parses incoming JSON automatically; when you convert the parsed object back to text, key order, whitespace and the way special characters are written may change. Yours won't match the format Meta used, and even a legitimate request will look "invalid". There is a thread on the n8n community forum for exactly this reason, describing signatures that never match: in the sender's raw body the slashes were escaped, while in the body shown by n8n they were not.
The solution is to take the hash over the raw bytes. The n8n Webhook node has a Raw Body option for this. The documentation describes this option as the node receiving data in a raw format such as JSON or XML. According to a community account, when this option is enabled, the request bytes are also kept as binary data alongside the parsed body. Since this behavior may change between versions, check it with a test request in your own setup.
Setup in n8n
The skeleton of the flow is as follows:
- Webhook node: Enable the Raw Body option. If you will use two separate methods, defining separate paths for GET (verification) and POST (events) simplifies the job.
- Signature calculation: You can use the Hmac operation of the Crypto node. According to the documentation, SHA256 is supported and the output can be HEX or BASE64. Since Meta sends the hash as hex, choose HEX and pass the raw body as input. If you prefer the Code node, you need Node's crypto module; for security reasons n8n restricts module use and requires the setting NODE_FUNCTION_ALLOW_BUILTIN=crypto. If you use a Task Runner, this setting must be made on the Task Runner, not on the main node.
- Comparison: Strip the "sha256=" prefix from the header value and compare it with the computed hex. If possible, use a timing-safe comparison; in the Code node, crypto.timingSafeEqual provides this.
- If node: For a request that doesn't match, stop the flow and return a 400-range code with Respond to Webhook. If it matches, continue.
By default, the Webhook node responds immediately with "Workflow got started". If you want to return different codes depending on the signature result, you need to switch the response mode to the Respond to Webhook node.
When Should You Return 200 to Meta?
According to Meta, failed deliveries are retried immediately and then with decreasing frequency for 7 days. So if your flow is slow or returns errors, the same message may reach you many times. Two measures help. First, verify the signature and return 200 right away, doing long-running work afterwards. Second, write each message's ID to a table and skip those already processed. Meta also says requests may arrive in batches, that a single request can contain up to 1000 updates, and that this is not guaranteed; process each request separately and in a loop.
Additional Layers
Although the signature check is sufficient, there are things you can add on top. Meta also documents mutual TLS (mTLS) support for webhooks; it must be enabled separately for each app. On the n8n side, the Webhook node has an IP allowlist option, and requests from outside get a 403, but for this to work you need to keep the addresses Meta uses up to date, and the address list can change. So put this alongside the signature, not in place of it. The endpoint must have a valid TLS certificate; Meta does not support self-signed certificates.
Your hosting choice also plays a role here. With n8n running on your own server, you manage the environment variables, reverse proxy settings and certificate yourself; in the cloud, some of that stays with the provider. For a comparison, see our self-hosted vs. cloud cost article. Where customer data coming from the webhook is stored is a separate topic: we covered it in the KVKK-compliant automation article.
A Practical Way to Test
First set it up with the Test URL, confirm the correct hash is being calculated, then switch to the Production URL. According to the n8n documentation, the test webhook is registered only while in listening mode, whereas the production address is registered when the workflow is published. If the Production address is defined in the Meta dashboard, a flow in test mode will see nothing. Then deliberately use a wrong secret key and check that the signature is rejected. A verification whose rejection side hasn't been tested has not proven that it works.
It is good for this foundation to be solid before setting up more advanced flows, such as linking messages coming from ads to sales; we covered the advertising side in the ctwa_clid article.
FAQ
Is verifying the WhatsApp webhook signature mandatory?
Meta's documentation explains that you should verify the signature and return 200 if it is valid. If you don't verify, anyone who knows your webhook address can send a fake message event, and your flow will take it for a real customer message.
Why can't I calculate the signature from the parsed JSON?
HMAC is calculated over the exact bytes of the body. When you parse the JSON and rewrite it, whitespace, escape characters and field order may change, and the hash won't match. That's why you need to work with the raw body.
What happens if Meta sends the same event more than once?
Meta retries failed deliveries immediately and then with decreasing frequency for 7 days. So your flow must not process the same message ID a second time.
Sources: Meta for Developers, "Create a webhook endpoint" · n8n Docs, Webhook node · n8n Docs, Crypto node · n8n Docs, Modules in the Code node · n8n Community, HMAC verification example · n8n Community, raw body issue. The interface and behavior may vary between versions; test on your own setup.
Let's review the security of your webhook flow together
Let's review signature verification, duplicate events and error handling in your WhatsApp flow in half an hour.