{"id":505,"date":"2026-10-28T16:04:36","date_gmt":"2026-10-28T16:04:36","guid":{"rendered":"https:\/\/vectalk.com.br\/blog\/whatsapp-webhook-signature-validation\/"},"modified":"2026-10-28T16:04:50","modified_gmt":"2026-10-28T16:04:50","slug":"whatsapp-webhook-signature-validation","status":"publish","type":"post","link":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/","title":{"rendered":"How to validate the WhatsApp webhook signature (X-Hub-Signature-256)"},"content":{"rendered":"\n<p><em>Updated on October 28, 2026<\/em><\/p>\n\n\n\n<p>To validate a WhatsApp webhook signature, compute an HMAC SHA-256 of the request body using your Meta <strong>app secret<\/strong> as the key, then compare it with the <code>X-Hub-Signature-256<\/code> header, which arrives as <code>sha256=&lt;hash&gt;<\/code>. If they match, the event came from Meta and was not tampered with. The mistake that breaks validation most often is hashing the JSON your framework already parsed instead of the exact bytes that arrived.<\/p>\n\n\n\n<p>This guide walks through the steps, ready-to-use code for Node.js (Express), Python (FastAPI and Flask) and PHP, and the most common pitfalls. For the bigger picture of the platform, see our <a href=\"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-cloud-api-en\/\">WhatsApp Cloud API guide<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why validate the signature<\/h2>\n\n\n\n<p>Your webhook URL is public. Anyone who finds it can send a <code>POST<\/code> with JSON shaped like Meta&#8217;s, faking a customer message, a button reply that confirms a payment, or a <code>delivered<\/code> status that never happened. Without validation, your system accepts all of it.<\/p>\n\n\n\n<p>Meta&#8217;s <a href=\"https:\/\/developers.facebook.com\/docs\/graph-api\/webhooks\/getting-started\" target=\"_blank\" rel=\"noreferrer noopener\">Graph API webhooks documentation<\/a> says every event notification is signed with SHA-256 and the signature is sent in the <code>X-Hub-Signature-256<\/code> header, prefixed with <code>sha256=<\/code>. Validation is optional, but Meta recommends it. In practice, it is what separates a trustworthy webhook from an open endpoint.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The two requests your endpoint receives<\/h2>\n\n\n\n<p>The same URL receives two kinds of calls, each with its own check:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Request<\/th><th>When it happens<\/th><th>What to check<\/th><th>Response<\/th><\/tr><\/thead><tbody><tr><td>Verification <code>GET<\/code><\/td><td>When you set or change the URL in the app dashboard<\/td><td><code>hub.mode<\/code> equals <code>subscribe<\/code> and <code>hub.verify_token<\/code> matches the token you configured<\/td><td><code>200<\/code> with the <code>hub.challenge<\/code> value<\/td><\/tr><tr><td>Event <code>POST<\/code><\/td><td>Every message, status or account change<\/td><td><code>X-Hub-Signature-256<\/code> against the HMAC of the raw body<\/td><td>Fast <code>200<\/code>; process in a queue<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>According to <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/create-webhook-endpoint\" target=\"_blank\" rel=\"noreferrer noopener\">Create a webhook endpoint<\/a>, a valid verification request gets a <code>200<\/code> with <code>hub.challenge<\/code>; an invalid one gets anything other than <code>200<\/code> (a 403, for example). The same page requires a valid TLS certificate on the endpoint: self-signed certificates are not supported.<\/p>\n\n\n\n<p>The <em>verify token<\/em> and the <em>app secret<\/em> are different things. The verify token is a string you make up and enter in the dashboard; it only matters for the <code>GET<\/code>. The app secret is generated by Meta, lives under <strong>App settings &gt; Basic<\/strong>, and is what signs the <code>POST<\/code> requests.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step-by-step validation<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Read the request body <strong>as bytes<\/strong>, before any JSON parser touches it.<\/li>\n\n\n<li>Compute <code>HMAC-SHA256(key = app secret, message = raw body)<\/code> and hex-encode it.<\/li>\n\n\n<li>Build <code>sha256=<\/code> + hash and compare it with the <code>X-Hub-Signature-256<\/code> header using a constant-time comparison.<\/li>\n\n\n<li>If it does not match, return 401 or 403 and drop the event.<\/li>\n\n\n<li>If it matches, parse the JSON, queue the event and return <code>200<\/code>.<\/li>\n\n<\/ol>\n\n\n\n<p>Constant-time comparison (<code>crypto.timingSafeEqual<\/code>, <code>hmac.compare_digest<\/code>, <code>hash_equals<\/code>) keeps an attacker from guessing the hash byte by byte by measuring response times.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Node.js example with Express<\/h2>\n\n\n\n<p>The key is <code>express.raw()<\/code> on the webhook route, so <code>req.body<\/code> is a <code>Buffer<\/code> with the original bytes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-js\">import express from 'express';\nimport crypto from 'node:crypto';\n\nconst app = express();\nconst APP_SECRET = process.env.META_APP_SECRET;\nconst VERIFY_TOKEN = process.env.WEBHOOK_VERIFY_TOKEN;\n\n\/\/ Verification GET\napp.get('\/webhook', (req, res) =&gt; {\n  const mode = req.query['hub.mode'];\n  const token = req.query['hub.verify_token'];\n  const challenge = req.query['hub.challenge'];\n  if (mode === 'subscribe' &amp;&amp; token === VERIFY_TOKEN) {\n    return res.status(200).send(challenge);\n  }\n  return res.sendStatus(403);\n});\n\n\/\/ Event POST: raw body on this route only\napp.post('\/webhook', express.raw({ type: 'application\/json' }), (req, res) =&gt; {\n  const received = req.get('X-Hub-Signature-256') || '';\n  const expected = 'sha256=' + crypto\n    .createHmac('sha256', APP_SECRET)\n    .update(req.body)\n    .digest('hex');\n\n  const a = Buffer.from(received);\n  const b = Buffer.from(expected);\n  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {\n    return res.sendStatus(401);\n  }\n\n  const event = JSON.parse(req.body.toString('utf8'));\n  res.sendStatus(200);\n  eventQueue.push(event); \/\/ process outside the request cycle\n});\n\napp.use(express.json()); \/\/ other routes can parse JSON as usual\napp.listen(3000);<\/code><\/pre>\n\n\n\n<p>If your app already registers <code>app.use(express.json())<\/code> before this route, the body arrives parsed and the hash will not match. Either declare the webhook route before the global middleware, or keep the raw bytes inside <code>express.json<\/code> itself:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-js\">app.use(express.json({\n  verify: (req, _res, buf) =&gt; { req.rawBody = buf; },\n}));\n\/\/ then pass req.rawBody to createHmac<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Python example with FastAPI<\/h2>\n\n\n\n<p>In FastAPI, <code>await request.body()<\/code> returns the exact bytes. Do not sign the Pydantic model of the body:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">import hashlib\nimport hmac\nimport json\nimport os\n\nfrom fastapi import FastAPI, HTTPException, Request, Response\n\napp = FastAPI()\nAPP_SECRET = os.environ[\"META_APP_SECRET\"].encode()\nVERIFY_TOKEN = os.environ[\"WEBHOOK_VERIFY_TOKEN\"]\n\n\n@app.get(\"\/webhook\")\nasync def verify(request: Request):\n    p = request.query_params\n    if p.get(\"hub.mode\") == \"subscribe\" and p.get(\"hub.verify_token\") == VERIFY_TOKEN:\n        return Response(content=p.get(\"hub.challenge\", \"\"), media_type=\"text\/plain\")\n    raise HTTPException(status_code=403)\n\n\n@app.post(\"\/webhook\")\nasync def receive(request: Request):\n    body = await request.body()\n    received = request.headers.get(\"x-hub-signature-256\", \"\")\n    expected = \"sha256=\" + hmac.new(APP_SECRET, body, hashlib.sha256).hexdigest()\n    if not hmac.compare_digest(received, expected):\n        raise HTTPException(status_code=401)\n\n    event = json.loads(body)\n    # enqueue the event and respond quickly\n    return {\"ok\": True}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Python example with Flask<\/h2>\n\n\n\n<p>In Flask, use <code>request.get_data()<\/code>, which returns the raw body:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-python\">import hashlib\nimport hmac\nimport os\n\nfrom flask import Flask, abort, request\n\napp = Flask(__name__)\nAPP_SECRET = os.environ[\"META_APP_SECRET\"].encode()\n\n\n@app.post(\"\/webhook\")\ndef receive():\n    body = request.get_data()\n    received = request.headers.get(\"X-Hub-Signature-256\", \"\")\n    expected = \"sha256=\" + hmac.new(APP_SECRET, body, hashlib.sha256).hexdigest()\n    if not hmac.compare_digest(received, expected):\n        abort(401)\n    event = request.get_json()\n    return \"\", 200<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">PHP example<\/h2>\n\n\n\n<p>In PHP, the raw body comes from <code>php:\/\/input<\/code>. Watch the <code>GET<\/code>: PHP replaces dots with underscores in parameter names, so <code>hub.verify_token<\/code> becomes <code>$_GET['hub_verify_token']<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-php\">&lt;?php\n$appSecret = getenv('META_APP_SECRET');\n$verifyToken = getenv('WEBHOOK_VERIFY_TOKEN');\n\nif ($_SERVER['REQUEST_METHOD'] === 'GET') {\n    if (($_GET['hub_mode'] ?? '') === 'subscribe'\n        &amp;&amp; hash_equals($verifyToken, $_GET['hub_verify_token'] ?? '')) {\n        echo $_GET['hub_challenge'] ?? '';\n        exit;\n    }\n    http_response_code(403);\n    exit;\n}\n\n$body = file_get_contents('php:\/\/input');\n$received = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';\n$expected = 'sha256=' . hash_hmac('sha256', $body, $appSecret);\n\nif (!hash_equals($expected, $received)) {\n    http_response_code(401);\n    exit;\n}\n\n$event = json_decode($body, true);\nhttp_response_code(200);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Common reasons the signature does not match<\/h2>\n\n\n\n<p><strong>Hashing parsed JSON.<\/strong> This is the number one mistake. <code>JSON.stringify(req.body)<\/code> or <code>json.dumps(payload)<\/code> produces text that differs from the original: whitespace, key order and character escaping change. One different byte changes the whole hash. Always sign the bytes you received.<\/p>\n\n\n\n<p><strong>Wrong encoding.<\/strong> Converting the body to a string with the wrong charset (Latin-1 instead of UTF-8, for example) alters accented names and emoji. Stay in bytes until validation is done.<\/p>\n\n\n\n<p><strong>Wrong secret.<\/strong> The right value is the app secret of the app subscribed to the webhook. It is not the access token, not the verify token, and not the secret of another app in the same business portfolio. If you run separate apps for staging and production, each environment needs its own.<\/p>\n\n\n\n<p><strong>Ignoring the prefix.<\/strong> The header carries <code>sha256=<\/code> before the hash. Either compare the full string (as in the examples) or strip the prefix on both sides.<\/p>\n\n\n\n<p><strong>Middleware or gateways changing the body.<\/strong> Proxies, API gateways and serverless platforms sometimes reformat or encode the body before your code sees it. If validation fails only in production, log the received body length and compare it with <code>Content-Length<\/code>.<\/p>\n\n\n\n<p><strong>Secret rotated, server not updated.<\/strong> After you reset the app secret in the dashboard, events are signed with the new value. If your server rejects everything with 401, Meta keeps retrying: per the <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/overview\" target=\"_blank\" rel=\"noreferrer noopener\">webhooks overview<\/a>, deliveries that do not get a <code>200<\/code> are retried with decreasing frequency for up to 7 days, which can produce duplicate events. Monitor your endpoint&#8217;s 401 rate to catch this early.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Good practices after validation<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Respond fast.<\/strong> Validate, enqueue, return <code>200<\/code>. Heavy work (AI, CRM, database) belongs in a worker.<\/li>\n\n\n<li><strong>Handle duplicates.<\/strong> Use the message (<code>wamid<\/code>) or status <code>id<\/code> as an idempotency key, since retries can repeat events.<\/li>\n\n\n<li><strong>Accept large payloads.<\/strong> The webhooks overview says a payload can be up to 3 MB; check your server&#8217;s body size limit.<\/li>\n\n\n<li><strong>Do not rely on IP allowlists.<\/strong> Meta changes its IP addresses periodically and suggests mTLS instead.<\/li>\n\n\n<li><strong>Test locally<\/strong> through a tunnel before deploying; see <a href=\"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/\">how to test the WhatsApp webhook locally with ngrok<\/a>.<\/li>\n\n<\/ul>\n\n\n\n<p>To send a signed test event to your own environment, compute the hash with the same secret:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">body='{\"object\":\"whatsapp_business_account\",\"entry\":[]}'\nsignature=$(printf '%s' \"$body\" | openssl dgst -sha256 -hmac \"$META_APP_SECRET\" | sed 's\/^.* \/\/')\ncurl -X POST http:\/\/localhost:3000\/webhook \\\n  -H \"Content-Type: application\/json\" \\\n  -H \"X-Hub-Signature-256: sha256=$signature\" \\\n  -d \"$body\"<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">How to do this with Vectalk<\/h2>\n\n\n\n<p>Vectalk is a proxy for the official Cloud API: the Hub receives events from Meta and forwards the same payload described in Meta&#8217;s documentation to the URLs you register, with no proprietary format. You can register as many URLs per instance as you need with <code>POST https:\/\/apis.vectalk.com.br\/api\/webhooks<\/code>, and your endpoint should return <code>200<\/code> within 20 seconds, per the <a href=\"https:\/\/docs.vectalk.com.br\/concepts\/webhooks\">Vectalk webhooks docs<\/a>. The <code>X-Hub-Signature-256<\/code> header covered here is what Meta sends to whoever receives directly from the app; to learn how to authenticate deliveries made by the Hub in your setup, check the docs or reach the team through the <a href=\"https:\/\/vectalk.com.br\/contato.html\">contact page<\/a>. Payload parsing, queueing and idempotency stay the same. To get started, sign up at <a href=\"https:\/\/apps.vectalk.com.br\/register\">apps.vectalk.com.br\/register<\/a>.<\/p>\n\n\n\n<p>With a secure webhook in place, the natural next steps are <a href=\"https:\/\/vectalk.com.br\/blog\/en\/download-media-whatsapp-webhook\/\">downloading the media customers send<\/a> and pacing your replies so you do not hit the <a href=\"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-api-rate-limit-throughput\/\">WhatsApp API throughput and rate limits<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Frequently asked questions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Where do I find the app secret?<\/h3>\n\n\n\n<p>In the Meta developer dashboard, under <strong>App settings &gt; Basic<\/strong>, in the &#8220;App secret&#8221; field. Keep it in an environment variable or a secrets manager, never in the repository.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Should the verify token be the same as the app secret?<\/h3>\n\n\n\n<p>No, and it should not be. The verify token is a string you choose and is only used for the verification <code>GET<\/code>. The app secret is generated by Meta and signs <code>POST<\/code> events.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why does the GET verification pass but the POST signature fail?<\/h3>\n\n\n\n<p>Because they are independent checks. The <code>GET<\/code> only compares the verify token. When the <code>POST<\/code> fails, the hash is almost always being computed over parsed JSON or with another app&#8217;s secret.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What status code should I return when the signature is invalid?<\/h3>\n\n\n\n<p>Anything other than <code>200<\/code>, such as 401 or 403. Keep in mind that rejected legitimate deliveries are retried, so an outdated secret shows up as a flood of 401s and late events.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need signature validation in a test environment?<\/h3>\n\n\n\n<p>Yes. Validating from day one keeps unchecked code out of production. To test without Meta, generate the signature with <code>openssl<\/code> as shown above. Payload details are in <a href=\"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-api-message-status-webhook\/\">WhatsApp API message status webhooks<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Official sources consulted on October 28, 2026:<\/strong> <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/create-webhook-endpoint\" target=\"_blank\" rel=\"noreferrer noopener\">Create a webhook endpoint (Meta)<\/a>, <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/overview\" target=\"_blank\" rel=\"noreferrer noopener\">WhatsApp webhooks overview (Meta)<\/a>, <a href=\"https:\/\/developers.facebook.com\/docs\/graph-api\/webhooks\/getting-started\" target=\"_blank\" rel=\"noreferrer noopener\">Graph API webhooks: getting started (Meta)<\/a>, <a href=\"https:\/\/docs.vectalk.com.br\/concepts\/webhooks\">Vectalk webhooks documentation<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to verify the X-Hub-Signature-256 header on WhatsApp Business API webhooks with HMAC SHA-256, with Node.js, Python and PHP code and the mistakes that break it.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[],"class_list":["post-505","post","type-post","status-publish","format-standard","hentry","category-blog"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Validate WhatsApp Webhook Signature (X-Hub-Signature-256)<\/title>\n<meta name=\"description\" content=\"Validate the WhatsApp webhook signature: HMAC SHA-256 of the raw body with your app secret, with Node.js, Python and PHP examples and common pitfalls.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Validate WhatsApp Webhook Signature (X-Hub-Signature-256)\" \/>\n<meta property=\"og:description\" content=\"Validate the WhatsApp webhook signature: HMAC SHA-256 of the raw body with your app secret, with Node.js, Python and PHP examples and common pitfalls.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/\" \/>\n<meta property=\"og:site_name\" content=\"Blog Vectalk\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-28T16:04:36+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-28T16:04:50+00:00\" \/>\n<meta name=\"author\" content=\"wanderleyfcunha\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"wanderleyfcunha\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/whatsapp-webhook-signature-validation\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/whatsapp-webhook-signature-validation\\\/\"},\"author\":{\"name\":\"wanderleyfcunha\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\"},\"headline\":\"How to validate the WhatsApp webhook signature (X-Hub-Signature-256)\",\"datePublished\":\"2026-10-28T16:04:36+00:00\",\"dateModified\":\"2026-10-28T16:04:50+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/whatsapp-webhook-signature-validation\\\/\"},\"wordCount\":1268,\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/whatsapp-webhook-signature-validation\\\/\",\"url\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/whatsapp-webhook-signature-validation\\\/\",\"name\":\"Validate WhatsApp Webhook Signature (X-Hub-Signature-256)\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#website\"},\"datePublished\":\"2026-10-28T16:04:36+00:00\",\"dateModified\":\"2026-10-28T16:04:50+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\"},\"description\":\"Validate the WhatsApp webhook signature: HMAC SHA-256 of the raw body with your app secret, with Node.js, Python and PHP examples and common pitfalls.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/whatsapp-webhook-signature-validation\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/whatsapp-webhook-signature-validation\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/whatsapp-webhook-signature-validation\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to validate the WhatsApp webhook signature (X-Hub-Signature-256)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/\",\"name\":\"Blog Vectalk\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\",\"name\":\"wanderleyfcunha\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g\",\"caption\":\"wanderleyfcunha\"},\"sameAs\":[\"https:\\\/\\\/vectalk.com.br\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Validate WhatsApp Webhook Signature (X-Hub-Signature-256)","description":"Validate the WhatsApp webhook signature: HMAC SHA-256 of the raw body with your app secret, with Node.js, Python and PHP examples and common pitfalls.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/","og_locale":"en_US","og_type":"article","og_title":"Validate WhatsApp Webhook Signature (X-Hub-Signature-256)","og_description":"Validate the WhatsApp webhook signature: HMAC SHA-256 of the raw body with your app secret, with Node.js, Python and PHP examples and common pitfalls.","og_url":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/","og_site_name":"Blog Vectalk","article_published_time":"2026-10-28T16:04:36+00:00","article_modified_time":"2026-10-28T16:04:50+00:00","author":"wanderleyfcunha","twitter_card":"summary_large_image","twitter_misc":{"Written by":"wanderleyfcunha","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/#article","isPartOf":{"@id":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/"},"author":{"name":"wanderleyfcunha","@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b"},"headline":"How to validate the WhatsApp webhook signature (X-Hub-Signature-256)","datePublished":"2026-10-28T16:04:36+00:00","dateModified":"2026-10-28T16:04:50+00:00","mainEntityOfPage":{"@id":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/"},"wordCount":1268,"articleSection":["Blog"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/","url":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/","name":"Validate WhatsApp Webhook Signature (X-Hub-Signature-256)","isPartOf":{"@id":"https:\/\/vectalk.com.br\/blog\/#website"},"datePublished":"2026-10-28T16:04:36+00:00","dateModified":"2026-10-28T16:04:50+00:00","author":{"@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b"},"description":"Validate the WhatsApp webhook signature: HMAC SHA-256 of the raw body with your app secret, with Node.js, Python and PHP examples and common pitfalls.","breadcrumb":{"@id":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-webhook-signature-validation\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/vectalk.com.br\/blog\/"},{"@type":"ListItem","position":2,"name":"How to validate the WhatsApp webhook signature (X-Hub-Signature-256)"}]},{"@type":"WebSite","@id":"https:\/\/vectalk.com.br\/blog\/#website","url":"https:\/\/vectalk.com.br\/blog\/","name":"Blog Vectalk","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/vectalk.com.br\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b","name":"wanderleyfcunha","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/21c37077f1731eb558973f27a7b647d55f72fde5200a843ac5f5345b247ea725?s=96&d=mm&r=g","caption":"wanderleyfcunha"},"sameAs":["https:\/\/vectalk.com.br\/blog"]}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts\/505","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/comments?post=505"}],"version-history":[{"count":1,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts\/505\/revisions"}],"predecessor-version":[{"id":508,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts\/505\/revisions\/508"}],"wp:attachment":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/media?parent=505"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/categories?post=505"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/tags?post=505"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}