{"id":141,"date":"2026-10-25T12:58:34","date_gmt":"2026-10-25T12:58:34","guid":{"rendered":"https:\/\/vectalk.com.br\/blog\/test-whatsapp-webhook-locally\/"},"modified":"2026-10-25T13:09:52","modified_gmt":"2026-10-25T13:09:52","slug":"test-whatsapp-webhook-locally","status":"publish","type":"post","link":"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/","title":{"rendered":"How to test the WhatsApp webhook locally with ngrok"},"content":{"rendered":"\n<p><em>Updated on October 25, 2026<\/em><\/p>\n\n\n\n<p>To test the WhatsApp webhook locally, run your server on your machine, expose the port through an HTTPS tunnel (ngrok or cloudflared) and register the public URL as the Callback URL in your Meta app dashboard. The server must answer the verification GET by returning <code>hub.challenge<\/code> and, on POSTs, validate the <code>X-Hub-Signature-256<\/code> header with the app secret. You then see real events arrive in your terminal, with no deploy.<\/p>\n\n\n\n<p>This guide includes a complete Node.js server, the commands for both tunnels, the test button in Meta&#8217;s dashboard and the most common errors at this stage.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why you need a tunnel<\/h2>\n\n\n\n<p>Meta only delivers webhooks to a public HTTPS URL. The <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/create-webhook-endpoint\" target=\"_blank\" rel=\"noreferrer noopener\">webhooks documentation<\/a> requires a valid TLS certificate and states that self-signed certificates are not supported. <code>http:\/\/localhost:3000<\/code> meets neither requirement.<\/p>\n\n\n\n<p>A tunnel fixes this: an agent runs on your machine, opens an outbound connection to the service and gets a public HTTPS URL that forwards requests to your local port. No router port forwarding or static IP needed.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Tool<\/th><th>Command<\/th><th>Generated URL<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td>ngrok<\/td><td><code>ngrok http 3000<\/code><\/td><td>HTTPS, shown in the agent console<\/td><td>Requires an account and authtoken; has a traffic inspector and request replay<\/td><\/tr><tr><td>cloudflared (Quick Tunnel)<\/td><td><code>cloudflared tunnel --url http:\/\/localhost:3000<\/code><\/td><td>Temporary subdomain on <code>trycloudflare.com<\/code><\/td><td>No account needed; the address changes on every run; testing only<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">How Meta talks to your endpoint<\/h2>\n\n\n\n<p>There are two different requests on the same URL:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Verification GET.<\/strong> When you save or edit the Callback URL or verify token in the dashboard, Meta sends a GET with <code>hub.mode=subscribe<\/code>, <code>hub.challenge<\/code> (a random string) and <code>hub.verify_token<\/code> (the value you typed in the dashboard). If the token matches, respond <code>200<\/code> with the <code>hub.challenge<\/code> value as the body. If it doesn&#8217;t, respond <code>403<\/code> or any non-200 code.<\/li>\n\n\n<li><strong>Event POST.<\/strong> Every incoming message or status change arrives as JSON with the header <code>X-Hub-Signature-256: sha256=&lt;hash&gt;<\/code>. The hash is an HMAC-SHA256 of the body, keyed with the app secret. Respond <code>200<\/code> to acknowledge.<\/li>\n\n<\/ol>\n\n\n\n<p>If a POST fails, Meta retries immediately and then several more times, with decreasing frequency, for up to 7 days. Deliveries not acknowledged in that period are dropped. So the same event can arrive more than once, and your server must deduplicate. Meta also notes there is no API to fetch past webhooks: store what you receive.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Node.js test server<\/h2>\n\n\n\n<p>The example uses Express. The key detail is keeping the <strong>raw request body<\/strong> to compute the signature. If you compute the HMAC over re-serialized JSON, the bytes may change and the comparison fails.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">mkdir webhook-local &amp;&amp; cd webhook-local\nnpm init -y\nnpm install express<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-js\">\/\/ server.js\nimport express from 'express';\nimport crypto from 'node:crypto';\n\nconst { VERIFY_TOKEN, APP_SECRET, PORT = 3000 } = process.env;\nconst app = express();\n\n\/\/ Keep the raw body to validate the signature\napp.use(express.json({\n  verify: (req, _res, buf) =&gt; { req.rawBody = buf; }\n}));\n\n\/\/ 1) 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\n  if (mode === 'subscribe' &amp;&amp; token === VERIFY_TOKEN) {\n    console.log('Webhook verified');\n    return res.status(200).send(challenge);\n  }\n  return res.sendStatus(403);\n});\n\n\/\/ 2) Events (POST)\napp.post('\/webhook', (req, res) =&gt; {\n  const header = req.get('x-hub-signature-256') || '';\n  const expected = 'sha256=' + crypto\n    .createHmac('sha256', APP_SECRET)\n    .update(req.rawBody || '')\n    .digest('hex');\n\n  const valid = header.length === expected.length &amp;&amp;\n    crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));\n\n  if (!valid) {\n    console.warn('Invalid signature');\n    return res.sendStatus(401);\n  }\n\n  res.sendStatus(200); \/\/ acknowledge fast\n\n  for (const entry of req.body.entry || []) {\n    for (const change of entry.changes || []) {\n      const v = change.value || {};\n      for (const m of v.messages || []) console.log('message', m.from, m.type, m.id);\n      for (const s of v.statuses || []) console.log('status', s.id, s.status);\n    }\n  }\n});\n\napp.listen(PORT, () =&gt; console.log(`Listening on http:\/\/localhost:${PORT}`));<\/code><\/pre>\n\n\n\n<p>To run it, add <code>\"type\": \"module\"<\/code> to <code>package.json<\/code> and start it with the variables:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">VERIFY_TOKEN=any-string-you-choose APP_SECRET=your_app_secret node server.js<\/code><\/pre>\n\n\n\n<p><code>VERIFY_TOKEN<\/code> is any string you define and repeat in the dashboard. <code>APP_SECRET<\/code> is the app secret, found under <strong>App settings \u2192 Basic<\/strong> in the developer dashboard. Never commit it to the repository.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step by step with ngrok<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create an ngrok account and install the agent (on macOS, <code>brew install ngrok<\/code>).<\/li>\n\n\n<li>Connect the agent to your account:<\/li>\n\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">ngrok config add-authtoken YOUR_AUTHTOKEN<\/code><\/pre>\n\n\n\n<ol class=\"wp-block-list\">\n<li>With the server running on port 3000, open the tunnel:<\/li>\n\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">ngrok http 3000<\/code><\/pre>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Copy the HTTPS forwarding URL shown in the console.<\/li>\n\n\n<li>In your Meta app dashboard, go to <strong>WhatsApp \u2192 Webhooks \u2192 Configuration<\/strong> (menu names may vary), paste <code>https:\/\/YOUR-URL\/webhook<\/code> into <strong>Callback URL<\/strong>, enter the same <code>VERIFY_TOKEN<\/code> in <strong>Verify token<\/strong> and click <strong>Verify and save<\/strong>. Your terminal should print &#8220;Webhook verified&#8221;.<\/li>\n\n\n<li>In the field list, subscribe to the <code>messages<\/code> field.<\/li>\n\n\n<li>Click <strong>Test<\/strong> next to <code>messages<\/code>. Meta sends a sample payload that should show up in your terminal. According to <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/set-up-whatsapp-echo-bot\" target=\"_blank\" rel=\"noreferrer noopener\">Meta&#8217;s test webhook guide<\/a>, the <code>messages<\/code> test works with the app in either development or live mode.<\/li>\n\n\n<li>Send a real message from your phone to the test or production number connected to the app and watch the event arrive.<\/li>\n\n<\/ol>\n\n\n\n<p>The ngrok agent includes a traffic inspector, usually at <code>http:\/\/localhost:4040<\/code>, showing headers, body and response for each request. With full capture mode enabled on your account, the <a href=\"https:\/\/ngrok.com\/docs\/obs\/traffic-inspection\" target=\"_blank\" rel=\"noreferrer noopener\">Traffic Inspector<\/a> lets you replay a request to your server without waiting for Meta, which speeds up parser debugging a lot.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step by step with cloudflared<\/h2>\n\n\n\n<p>If you don&#8217;t want to create an account, Cloudflare&#8217;s Quick Tunnel works for quick tests:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">cloudflared tunnel --url http:\/\/localhost:3000<\/code><\/pre>\n\n\n\n<p>The command prints a <code>trycloudflare.com<\/code> URL. Use it in Meta&#8217;s dashboard the same way. Per <a href=\"https:\/\/developers.cloudflare.com\/cloudflare-one\/networks\/connectors\/cloudflare-tunnel\/do-more-with-tunnels\/trycloudflare\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudflare&#8217;s documentation<\/a>, Quick Tunnels are for testing and development, have no uptime guarantee, allow up to 200 in-flight requests and generate a new hostname on every run. Since the URL changes, you have to verify again in the dashboard each time you restart the tunnel.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Common errors and fixes<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Symptom<\/th><th>Likely cause<\/th><th>Fix<\/th><\/tr><\/thead><tbody><tr><td>&#8220;The callback URL couldn&#8217;t be validated&#8221; in the dashboard<\/td><td>Token mismatch, server down, wrong route, or JSON returned instead of the challenge text<\/td><td>Return only the <code>hub.challenge<\/code> value, with status 200, on the exact registered route<\/td><\/tr><tr><td>Verification GET works but no POST arrives<\/td><td><code>messages<\/code> field not subscribed, or the WhatsApp account isn&#8217;t linked to the app<\/td><td>Subscribe to the field and check which app the account is linked to<\/td><\/tr><tr><td>Every signature is invalid<\/td><td>HMAC computed over re-serialized JSON, wrong secret, or header compared without the <code>sha256=<\/code> prefix<\/td><td>Use the raw body and the app secret (not the access token)<\/td><\/tr><tr><td>The same event arrives several times<\/td><td>Slow or non-200 response triggering retries<\/td><td>Respond 200 before processing and deduplicate by <code>id<\/code><\/td><\/tr><tr><td>Stopped working after restarting the tunnel<\/td><td>The public URL changed<\/td><td>Update the Callback URL and verify again<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>One more caution: the webhook is configured per app. If you point the production app&#8217;s Callback URL at your machine, production stops receiving events. Use a separate development app for local testing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">From testing to production<\/h2>\n\n\n\n<p>A tunnel is a development tool. In production, the endpoint needs its own domain, a valid certificate, a queue to process events outside the request and storage for payloads. The <a href=\"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-api-message-status-webhook\/\">message status<\/a> guide shows how to handle <code>sent<\/code>, <code>delivered<\/code>, <code>read<\/code> and <code>failed<\/code>. When webhooks carry errors, the <a href=\"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-api-error-codes\/\">WhatsApp API errors<\/a> table helps you decide what to do. If the app still uses a temporary token, switch to a <a href=\"https:\/\/vectalk.com.br\/blog\/en\/whatsapp-api-permanent-access-token\/\">permanent access token<\/a> before going live.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How this works with Vectalk<\/h2>\n\n\n\n<p>With Vectalk, events are Meta&#8217;s own, in the same <code>entry[].changes[].value<\/code> format, so the parser above works unchanged. What changes is registration: instead of Meta&#8217;s dashboard, you register the URL through the API with <code>instance_id<\/code>, <code>url<\/code> (HTTPS) and <code>name<\/code>, and you can have several URLs per instance, all receiving the same event.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"language-bash\">curl -X POST https:\/\/apis.vectalk.com.br\/api\/webhooks \\\n  -H \"Authorization: Bearer YOUR_TOKEN\" \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\n    \"instance_id\": \"YOUR_INSTANCE\",\n    \"url\": \"https:\/\/YOUR-TUNNEL-URL\/webhook\",\n    \"name\": \"Local dev\"\n  }'<\/code><\/pre>\n\n\n\n<p>This lets you register the tunnel URL as an extra webhook without removing the production one, and turn it off later with <code>active: false<\/code> on the <a href=\"https:\/\/docs.vectalk.com.br\/api-reference\/webhooks\/update\">update webhook<\/a> endpoint. Vectalk expects a <code>200<\/code> within 20 seconds and resends the event otherwise, according to the <a href=\"https:\/\/docs.vectalk.com.br\/concepts\/webhooks\">webhooks documentation<\/a>. Before enforcing signature validation in this flow, check the documentation and the tunnel inspector to see which headers arrive.<\/p>\n\n\n\n<p>Create your account at <a href=\"https:\/\/apps.vectalk.com.br\/register\">apps.vectalk.com.br\/register<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Frequently asked questions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Can I register http:\/\/localhost as a WhatsApp webhook?<\/h3>\n\n\n\n<p>No. Meta requires a public HTTPS URL with a valid certificate. To test on your machine, use a tunnel such as ngrok or cloudflared.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What should I return on the verification GET?<\/h3>\n\n\n\n<p>Status 200 with the <code>hub.challenge<\/code> value as the body, no JSON and no quotes, as long as <code>hub.verify_token<\/code> matches the token you registered.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Which secret validates X-Hub-Signature-256?<\/h3>\n\n\n\n<p>The App Secret, not the access token or the verify token. The hash is an HMAC-SHA256 of the request body, compared with the value after <code>sha256=<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why do I receive the same webhook twice?<\/h3>\n\n\n\n<p>Because Meta retries when it doesn&#8217;t get a 200, for up to 7 days. Respond quickly and deduplicate by the message or status <code>id<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can I use ngrok in production?<\/h3>\n\n\n\n<p>It is designed to expose local services during development. For production, host the endpoint on a server with its own domain and certificate.<\/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 25, 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 (WhatsApp)<\/a>, <a href=\"https:\/\/developers.facebook.com\/documentation\/business-messaging\/whatsapp\/webhooks\/set-up-whatsapp-echo-bot\" target=\"_blank\" rel=\"noreferrer noopener\">Set up a WhatsApp echo bot<\/a>, <a href=\"https:\/\/developers.facebook.com\/docs\/graph-api\/webhooks\/getting-started\" target=\"_blank\" rel=\"noreferrer noopener\">Graph API webhooks: getting started<\/a>, <a href=\"https:\/\/ngrok.com\/docs\/share-localhost\/quickstart\" target=\"_blank\" rel=\"noreferrer noopener\">ngrok: Share Localhost Quickstart<\/a>, <a href=\"https:\/\/ngrok.com\/docs\/share-localhost\/webhooks\" target=\"_blank\" rel=\"noreferrer noopener\">ngrok: Test Webhooks Locally<\/a>, <a href=\"https:\/\/ngrok.com\/docs\/obs\/traffic-inspection\" target=\"_blank\" rel=\"noreferrer noopener\">ngrok: Traffic Inspector<\/a>, <a href=\"https:\/\/developers.cloudflare.com\/cloudflare-one\/networks\/connectors\/cloudflare-tunnel\/do-more-with-tunnels\/trycloudflare\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudflare Quick Tunnels<\/a>, <a href=\"https:\/\/docs.vectalk.com.br\/concepts\/webhooks\">Vectalk docs: webhooks<\/a>, <a href=\"https:\/\/docs.vectalk.com.br\/api-reference\/webhooks\/update\">Vectalk docs: update webhook<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Step by step to receive WhatsApp Business API webhooks on localhost with ngrok or cloudflared, answer hub.challenge and validate the X-Hub-Signature-256 header.<\/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-141","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>Test WhatsApp Webhooks Locally with ngrok | Vectalk<\/title>\n<meta name=\"description\" content=\"How to test a WhatsApp webhook on your machine: ngrok or cloudflared tunnel, hub.challenge verification and X-Hub-Signature-256 validation in Node.js.\" \/>\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\/test-whatsapp-webhook-locally\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Test WhatsApp Webhooks Locally with ngrok | Vectalk\" \/>\n<meta property=\"og:description\" content=\"How to test a WhatsApp webhook on your machine: ngrok or cloudflared tunnel, hub.challenge verification and X-Hub-Signature-256 validation in Node.js.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/\" \/>\n<meta property=\"og:site_name\" content=\"Blog Vectalk\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-25T12:58:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-25T13:09:52+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=\"8 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\\\/test-whatsapp-webhook-locally\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/test-whatsapp-webhook-locally\\\/\"},\"author\":{\"name\":\"wanderleyfcunha\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\"},\"headline\":\"How to test the WhatsApp webhook locally with ngrok\",\"datePublished\":\"2026-10-25T12:58:34+00:00\",\"dateModified\":\"2026-10-25T13:09:52+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/test-whatsapp-webhook-locally\\\/\"},\"wordCount\":1282,\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/test-whatsapp-webhook-locally\\\/\",\"url\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/test-whatsapp-webhook-locally\\\/\",\"name\":\"Test WhatsApp Webhooks Locally with ngrok | Vectalk\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#website\"},\"datePublished\":\"2026-10-25T12:58:34+00:00\",\"dateModified\":\"2026-10-25T13:09:52+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/#\\\/schema\\\/person\\\/ff36ecd9d8257856f227f8e5d639db2b\"},\"description\":\"How to test a WhatsApp webhook on your machine: ngrok or cloudflared tunnel, hub.challenge verification and X-Hub-Signature-256 validation in Node.js.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/test-whatsapp-webhook-locally\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/test-whatsapp-webhook-locally\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/en\\\/test-whatsapp-webhook-locally\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/vectalk.com.br\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to test the WhatsApp webhook locally with ngrok\"}]},{\"@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":"Test WhatsApp Webhooks Locally with ngrok | Vectalk","description":"How to test a WhatsApp webhook on your machine: ngrok or cloudflared tunnel, hub.challenge verification and X-Hub-Signature-256 validation in Node.js.","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\/test-whatsapp-webhook-locally\/","og_locale":"en_US","og_type":"article","og_title":"Test WhatsApp Webhooks Locally with ngrok | Vectalk","og_description":"How to test a WhatsApp webhook on your machine: ngrok or cloudflared tunnel, hub.challenge verification and X-Hub-Signature-256 validation in Node.js.","og_url":"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/","og_site_name":"Blog Vectalk","article_published_time":"2026-10-25T12:58:34+00:00","article_modified_time":"2026-10-25T13:09:52+00:00","author":"wanderleyfcunha","twitter_card":"summary_large_image","twitter_misc":{"Written by":"wanderleyfcunha","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/#article","isPartOf":{"@id":"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/"},"author":{"name":"wanderleyfcunha","@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b"},"headline":"How to test the WhatsApp webhook locally with ngrok","datePublished":"2026-10-25T12:58:34+00:00","dateModified":"2026-10-25T13:09:52+00:00","mainEntityOfPage":{"@id":"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/"},"wordCount":1282,"articleSection":["Blog"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/","url":"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/","name":"Test WhatsApp Webhooks Locally with ngrok | Vectalk","isPartOf":{"@id":"https:\/\/vectalk.com.br\/blog\/#website"},"datePublished":"2026-10-25T12:58:34+00:00","dateModified":"2026-10-25T13:09:52+00:00","author":{"@id":"https:\/\/vectalk.com.br\/blog\/#\/schema\/person\/ff36ecd9d8257856f227f8e5d639db2b"},"description":"How to test a WhatsApp webhook on your machine: ngrok or cloudflared tunnel, hub.challenge verification and X-Hub-Signature-256 validation in Node.js.","breadcrumb":{"@id":"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/vectalk.com.br\/blog\/en\/test-whatsapp-webhook-locally\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/vectalk.com.br\/blog\/"},{"@type":"ListItem","position":2,"name":"How to test the WhatsApp webhook locally with ngrok"}]},{"@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\/141","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=141"}],"version-history":[{"count":2,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts\/141\/revisions"}],"predecessor-version":[{"id":394,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/posts\/141\/revisions\/394"}],"wp:attachment":[{"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/media?parent=141"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/categories?post=141"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vectalk.com.br\/blog\/wp-json\/wp\/v2\/tags?post=141"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}