[{"data":1,"prerenderedAt":1267},["ShallowReactive",2],{"content-query-IieKb2o8jE":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"layout":10,"author":11,"tags":12,"categories":17,"date":19,"faq":20,"image":33,"body":34,"_type":1260,"_id":1261,"_source":1262,"_file":1263,"_stem":1264,"_extension":1265,"sitemap":1266},"/blog/whatsapp-cloud-api-webhooks","blog",false,"","WhatsApp Cloud API Webhooks: Setup, Verify Token and Payloads","Set up WhatsApp Cloud API webhooks step by step: the callback URL, the hub.challenge verify token handshake, message payload examples, X-Hub-Signature-256 verification and local testing.","post","Karolis Rusenas",[13,14,15,16],"whatsapp","meta","webhooks","cloud-api",[18],"tutorials","2026-07-07 10:30:00",[21,24,27,30],{"q":22,"a":23},"What is the WhatsApp webhook verify token?","A string you invent. When you save your callback URL in the Meta App Dashboard, Meta sends a GET request with hub.mode=subscribe, hub.verify_token and hub.challenge query parameters. Your endpoint must check the token matches the one you configured and respond with the raw hub.challenge value (HTTP 200). Until that handshake succeeds, no webhooks are delivered.",{"q":25,"a":26},"Why is my WhatsApp webhook not receiving messages?","Most commonly: the verify handshake never completed, you did not subscribe to the messages field after verifying, the phone number is not registered against your app, or your endpoint is not publicly reachable over HTTPS with a valid certificate. Verify the GET handshake works first, then check the webhook fields subscription.",{"q":28,"a":29},"How do I verify a WhatsApp webhook signature?","Meta signs each POST with HMAC-SHA256 of the raw body using your app secret, sent as sha256=\u003Chex> in the X-Hub-Signature-256 header. Recompute over the exact raw bytes and compare in constant time. Note the key is the app secret, not the verify token.",{"q":31,"a":32},"Can I receive WhatsApp webhooks on localhost?","Not directly — Meta requires a public HTTPS URL. Put a stable public endpoint in front (for example a Webhook Relay bucket) and forward events to localhost over an outbound connection, so you never expose your machine or re-verify the URL when you restart.","/images/blog/heroes/route.jpg",{"type":35,"children":36,"toc":1251},"root",[37,47,68,75,111,131,137,173,178,197,202,512,525,544,550,555,984,1020,1041,1047,1089,1095,1133,1139,1151,1184,1204,1210,1245],{"type":38,"tag":39,"props":40,"children":41},"element","p",{},[42],{"type":38,"tag":43,"props":44,"children":46},"img",{"alt":45,"src":33},"WhatsApp Cloud API webhooks",[],{"type":38,"tag":39,"props":48,"children":49},{},[50,53,59,61,66],{"type":51,"value":52},"text","Every incoming WhatsApp message, status update and delivery receipt reaches your code the same way: a ",{"type":38,"tag":54,"props":55,"children":56},"strong",{},[57],{"type":51,"value":58},"webhook from the WhatsApp Cloud API",{"type":51,"value":60},". Setting one up trips people in a specific spot — the ",{"type":38,"tag":54,"props":62,"children":63},{},[64],{"type":51,"value":65},"verify token handshake",{"type":51,"value":67}," — and then again when the payloads arrive nested four levels deep. This guide walks the whole path: callback URL, verification, payload shapes, signatures, and a sane local dev loop.",{"type":38,"tag":69,"props":70,"children":72},"h2",{"id":71},"how-cloud-api-webhooks-work",[73],{"type":51,"value":74},"How Cloud API webhooks work",{"type":38,"tag":39,"props":76,"children":77},{},[78,80,85,87,93,95,100,102,109],{"type":51,"value":79},"You configure a single ",{"type":38,"tag":54,"props":81,"children":82},{},[83],{"type":51,"value":84},"callback URL",{"type":51,"value":86}," for your Meta app. Meta first ",{"type":38,"tag":88,"props":89,"children":90},"em",{},[91],{"type":51,"value":92},"verifies",{"type":51,"value":94}," it (a GET handshake), then delivers events (POSTs) for every ",{"type":38,"tag":54,"props":96,"children":97},{},[98],{"type":51,"value":99},"webhook field",{"type":51,"value":101}," you subscribe to — for WhatsApp, the one you almost always want is ",{"type":38,"tag":103,"props":104,"children":106},"code",{"className":105},[],[107],{"type":51,"value":108},"messages",{"type":51,"value":110},".",{"type":38,"tag":39,"props":112,"children":113},{},[114,116,121,123,129],{"type":51,"value":115},"Requirements: public ",{"type":38,"tag":54,"props":117,"children":118},{},[119],{"type":51,"value":120},"HTTPS",{"type":51,"value":122}," URL with a valid certificate, answering within a few seconds. There is no way to point Meta directly at ",{"type":38,"tag":103,"props":124,"children":126},{"className":125},[],[127],{"type":51,"value":128},"localhost",{"type":51,"value":130}," — see the last section for the clean workaround.",{"type":38,"tag":69,"props":132,"children":134},{"id":133},"step-1-the-verify-token-handshake",[135],{"type":51,"value":136},"Step 1: The verify token handshake",{"type":38,"tag":39,"props":138,"children":139},{},[140,142,151,153,158,160,164,166,171],{"type":51,"value":141},"In the ",{"type":38,"tag":143,"props":144,"children":148},"a",{"href":145,"rel":146},"https://developers.facebook.com/apps/",[147],"nofollow",[149],{"type":51,"value":150},"Meta App Dashboard",{"type":51,"value":152}," open your app → ",{"type":38,"tag":54,"props":154,"children":155},{},[156],{"type":51,"value":157},"WhatsApp → Configuration → Webhook",{"type":51,"value":159},". You enter two things: the ",{"type":38,"tag":54,"props":161,"children":162},{},[163],{"type":51,"value":84},{"type":51,"value":165}," and a ",{"type":38,"tag":54,"props":167,"children":168},{},[169],{"type":51,"value":170},"verify token",{"type":51,"value":172}," — any string you choose.",{"type":38,"tag":39,"props":174,"children":175},{},[176],{"type":51,"value":177},"When you hit save, Meta sends:",{"type":38,"tag":179,"props":180,"children":183},"pre",{"className":181,"code":182,"language":51,"meta":7,"style":7},"language-text shiki shiki-themes github-dark","GET /your/webhook?hub.mode=subscribe&hub.verify_token=YOUR_TOKEN&hub.challenge=1158201444\n",[184],{"type":38,"tag":103,"props":185,"children":186},{"__ignoreMap":7},[187],{"type":38,"tag":188,"props":189,"children":192},"span",{"class":190,"line":191},"line",1,[193],{"type":38,"tag":188,"props":194,"children":195},{},[196],{"type":51,"value":182},{"type":38,"tag":39,"props":198,"children":199},{},[200],{"type":51,"value":201},"Your endpoint must check the token and echo the challenge:",{"type":38,"tag":179,"props":203,"children":207},{"className":204,"code":205,"language":206,"meta":7,"style":7},"language-js shiki shiki-themes github-dark","// Express\napp.get('/webhook', (req, res) => {\n  const { 'hub.mode': mode, 'hub.verify_token': token, 'hub.challenge': challenge } = req.query;\n  if (mode === 'subscribe' && token === process.env.VERIFY_TOKEN) {\n    return res.status(200).send(challenge);   // raw value, no JSON\n  }\n  res.sendStatus(403);\n});\n","js",[208],{"type":38,"tag":103,"props":209,"children":210},{"__ignoreMap":7},[211,220,284,365,418,466,475,503],{"type":38,"tag":188,"props":212,"children":213},{"class":190,"line":191},[214],{"type":38,"tag":188,"props":215,"children":217},{"style":216},"--shiki-default:#6A737D",[218],{"type":51,"value":219},"// Express\n",{"type":38,"tag":188,"props":221,"children":223},{"class":190,"line":222},2,[224,230,236,241,247,252,258,263,268,273,279],{"type":38,"tag":188,"props":225,"children":227},{"style":226},"--shiki-default:#E1E4E8",[228],{"type":51,"value":229},"app.",{"type":38,"tag":188,"props":231,"children":233},{"style":232},"--shiki-default:#B392F0",[234],{"type":51,"value":235},"get",{"type":38,"tag":188,"props":237,"children":238},{"style":226},[239],{"type":51,"value":240},"(",{"type":38,"tag":188,"props":242,"children":244},{"style":243},"--shiki-default:#9ECBFF",[245],{"type":51,"value":246},"'/webhook'",{"type":38,"tag":188,"props":248,"children":249},{"style":226},[250],{"type":51,"value":251},", (",{"type":38,"tag":188,"props":253,"children":255},{"style":254},"--shiki-default:#FFAB70",[256],{"type":51,"value":257},"req",{"type":38,"tag":188,"props":259,"children":260},{"style":226},[261],{"type":51,"value":262},", ",{"type":38,"tag":188,"props":264,"children":265},{"style":254},[266],{"type":51,"value":267},"res",{"type":38,"tag":188,"props":269,"children":270},{"style":226},[271],{"type":51,"value":272},") ",{"type":38,"tag":188,"props":274,"children":276},{"style":275},"--shiki-default:#F97583",[277],{"type":51,"value":278},"=>",{"type":38,"tag":188,"props":280,"children":281},{"style":226},[282],{"type":51,"value":283}," {\n",{"type":38,"tag":188,"props":285,"children":287},{"class":190,"line":286},3,[288,293,298,303,308,314,318,323,327,332,336,341,345,350,355,360],{"type":38,"tag":188,"props":289,"children":290},{"style":275},[291],{"type":51,"value":292},"  const",{"type":38,"tag":188,"props":294,"children":295},{"style":226},[296],{"type":51,"value":297}," { ",{"type":38,"tag":188,"props":299,"children":300},{"style":243},[301],{"type":51,"value":302},"'hub.mode'",{"type":38,"tag":188,"props":304,"children":305},{"style":226},[306],{"type":51,"value":307},": ",{"type":38,"tag":188,"props":309,"children":311},{"style":310},"--shiki-default:#79B8FF",[312],{"type":51,"value":313},"mode",{"type":38,"tag":188,"props":315,"children":316},{"style":226},[317],{"type":51,"value":262},{"type":38,"tag":188,"props":319,"children":320},{"style":243},[321],{"type":51,"value":322},"'hub.verify_token'",{"type":38,"tag":188,"props":324,"children":325},{"style":226},[326],{"type":51,"value":307},{"type":38,"tag":188,"props":328,"children":329},{"style":310},[330],{"type":51,"value":331},"token",{"type":38,"tag":188,"props":333,"children":334},{"style":226},[335],{"type":51,"value":262},{"type":38,"tag":188,"props":337,"children":338},{"style":243},[339],{"type":51,"value":340},"'hub.challenge'",{"type":38,"tag":188,"props":342,"children":343},{"style":226},[344],{"type":51,"value":307},{"type":38,"tag":188,"props":346,"children":347},{"style":310},[348],{"type":51,"value":349},"challenge",{"type":38,"tag":188,"props":351,"children":352},{"style":226},[353],{"type":51,"value":354}," } ",{"type":38,"tag":188,"props":356,"children":357},{"style":275},[358],{"type":51,"value":359},"=",{"type":38,"tag":188,"props":361,"children":362},{"style":226},[363],{"type":51,"value":364}," req.query;\n",{"type":38,"tag":188,"props":366,"children":368},{"class":190,"line":367},4,[369,374,379,384,389,394,399,403,408,413],{"type":38,"tag":188,"props":370,"children":371},{"style":275},[372],{"type":51,"value":373},"  if",{"type":38,"tag":188,"props":375,"children":376},{"style":226},[377],{"type":51,"value":378}," (mode ",{"type":38,"tag":188,"props":380,"children":381},{"style":275},[382],{"type":51,"value":383},"===",{"type":38,"tag":188,"props":385,"children":386},{"style":243},[387],{"type":51,"value":388}," 'subscribe'",{"type":38,"tag":188,"props":390,"children":391},{"style":275},[392],{"type":51,"value":393}," &&",{"type":38,"tag":188,"props":395,"children":396},{"style":226},[397],{"type":51,"value":398}," token ",{"type":38,"tag":188,"props":400,"children":401},{"style":275},[402],{"type":51,"value":383},{"type":38,"tag":188,"props":404,"children":405},{"style":226},[406],{"type":51,"value":407}," process.env.",{"type":38,"tag":188,"props":409,"children":410},{"style":310},[411],{"type":51,"value":412},"VERIFY_TOKEN",{"type":38,"tag":188,"props":414,"children":415},{"style":226},[416],{"type":51,"value":417},") {\n",{"type":38,"tag":188,"props":419,"children":421},{"class":190,"line":420},5,[422,427,432,437,441,446,451,456,461],{"type":38,"tag":188,"props":423,"children":424},{"style":275},[425],{"type":51,"value":426},"    return",{"type":38,"tag":188,"props":428,"children":429},{"style":226},[430],{"type":51,"value":431}," res.",{"type":38,"tag":188,"props":433,"children":434},{"style":232},[435],{"type":51,"value":436},"status",{"type":38,"tag":188,"props":438,"children":439},{"style":226},[440],{"type":51,"value":240},{"type":38,"tag":188,"props":442,"children":443},{"style":310},[444],{"type":51,"value":445},"200",{"type":38,"tag":188,"props":447,"children":448},{"style":226},[449],{"type":51,"value":450},").",{"type":38,"tag":188,"props":452,"children":453},{"style":232},[454],{"type":51,"value":455},"send",{"type":38,"tag":188,"props":457,"children":458},{"style":226},[459],{"type":51,"value":460},"(challenge);   ",{"type":38,"tag":188,"props":462,"children":463},{"style":216},[464],{"type":51,"value":465},"// raw value, no JSON\n",{"type":38,"tag":188,"props":467,"children":469},{"class":190,"line":468},6,[470],{"type":38,"tag":188,"props":471,"children":472},{"style":226},[473],{"type":51,"value":474},"  }\n",{"type":38,"tag":188,"props":476,"children":478},{"class":190,"line":477},7,[479,484,489,493,498],{"type":38,"tag":188,"props":480,"children":481},{"style":226},[482],{"type":51,"value":483},"  res.",{"type":38,"tag":188,"props":485,"children":486},{"style":232},[487],{"type":51,"value":488},"sendStatus",{"type":38,"tag":188,"props":490,"children":491},{"style":226},[492],{"type":51,"value":240},{"type":38,"tag":188,"props":494,"children":495},{"style":310},[496],{"type":51,"value":497},"403",{"type":38,"tag":188,"props":499,"children":500},{"style":226},[501],{"type":51,"value":502},");\n",{"type":38,"tag":188,"props":504,"children":506},{"class":190,"line":505},8,[507],{"type":38,"tag":188,"props":508,"children":509},{"style":226},[510],{"type":51,"value":511},"});\n",{"type":38,"tag":39,"props":513,"children":514},{},[515,517,523],{"type":51,"value":516},"The two classic mistakes: responding with JSON (",{"type":38,"tag":103,"props":518,"children":520},{"className":519},[],[521],{"type":51,"value":522},"{\"hub.challenge\": …}",{"type":51,"value":524}," fails — Meta wants the raw value) and confusing the verify token with the app secret (they are unrelated; the token is only used in this handshake).",{"type":38,"tag":39,"props":526,"children":527},{},[528,530,535,537,542],{"type":51,"value":529},"After verification succeeds, click ",{"type":38,"tag":54,"props":531,"children":532},{},[533],{"type":51,"value":534},"Manage",{"type":51,"value":536}," and subscribe to the ",{"type":38,"tag":103,"props":538,"children":540},{"className":539},[],[541],{"type":51,"value":108},{"type":51,"value":543}," field — without this second step you stay silent.",{"type":38,"tag":69,"props":545,"children":547},{"id":546},"step-2-read-the-payload",[548],{"type":51,"value":549},"Step 2: Read the payload",{"type":38,"tag":39,"props":551,"children":552},{},[553],{"type":51,"value":554},"Deliveries are POSTs with a deeply nested envelope. An inbound text message:",{"type":38,"tag":179,"props":556,"children":560},{"className":557,"code":558,"language":559,"meta":7,"style":7},"language-json shiki shiki-themes github-dark","{\n  \"object\": \"whatsapp_business_account\",\n  \"entry\": [{\n    \"id\": \"WABA_ID\",\n    \"changes\": [{\n      \"field\": \"messages\",\n      \"value\": {\n        \"messaging_product\": \"whatsapp\",\n        \"metadata\": { \"display_phone_number\": \"15550001111\", \"phone_number_id\": \"PHONE_ID\" },\n        \"contacts\": [{ \"profile\": { \"name\": \"Alex Doe\" }, \"wa_id\": \"15557770000\" }],\n        \"messages\": [{\n          \"from\": \"15557770000\",\n          \"id\": \"wamid.HBgL...\",\n          \"timestamp\": \"1768472000\",\n          \"type\": \"text\",\n          \"text\": { \"body\": \"hello!\" }\n        }]\n      }\n    }]\n  }]\n}\n","json",[561],{"type":38,"tag":103,"props":562,"children":563},{"__ignoreMap":7},[564,572,594,607,628,640,661,674,695,746,807,820,841,863,885,907,939,948,957,966,975],{"type":38,"tag":188,"props":565,"children":566},{"class":190,"line":191},[567],{"type":38,"tag":188,"props":568,"children":569},{"style":226},[570],{"type":51,"value":571},"{\n",{"type":38,"tag":188,"props":573,"children":574},{"class":190,"line":222},[575,580,584,589],{"type":38,"tag":188,"props":576,"children":577},{"style":310},[578],{"type":51,"value":579},"  \"object\"",{"type":38,"tag":188,"props":581,"children":582},{"style":226},[583],{"type":51,"value":307},{"type":38,"tag":188,"props":585,"children":586},{"style":243},[587],{"type":51,"value":588},"\"whatsapp_business_account\"",{"type":38,"tag":188,"props":590,"children":591},{"style":226},[592],{"type":51,"value":593},",\n",{"type":38,"tag":188,"props":595,"children":596},{"class":190,"line":286},[597,602],{"type":38,"tag":188,"props":598,"children":599},{"style":310},[600],{"type":51,"value":601},"  \"entry\"",{"type":38,"tag":188,"props":603,"children":604},{"style":226},[605],{"type":51,"value":606},": [{\n",{"type":38,"tag":188,"props":608,"children":609},{"class":190,"line":367},[610,615,619,624],{"type":38,"tag":188,"props":611,"children":612},{"style":310},[613],{"type":51,"value":614},"    \"id\"",{"type":38,"tag":188,"props":616,"children":617},{"style":226},[618],{"type":51,"value":307},{"type":38,"tag":188,"props":620,"children":621},{"style":243},[622],{"type":51,"value":623},"\"WABA_ID\"",{"type":38,"tag":188,"props":625,"children":626},{"style":226},[627],{"type":51,"value":593},{"type":38,"tag":188,"props":629,"children":630},{"class":190,"line":420},[631,636],{"type":38,"tag":188,"props":632,"children":633},{"style":310},[634],{"type":51,"value":635},"    \"changes\"",{"type":38,"tag":188,"props":637,"children":638},{"style":226},[639],{"type":51,"value":606},{"type":38,"tag":188,"props":641,"children":642},{"class":190,"line":468},[643,648,652,657],{"type":38,"tag":188,"props":644,"children":645},{"style":310},[646],{"type":51,"value":647},"      \"field\"",{"type":38,"tag":188,"props":649,"children":650},{"style":226},[651],{"type":51,"value":307},{"type":38,"tag":188,"props":653,"children":654},{"style":243},[655],{"type":51,"value":656},"\"messages\"",{"type":38,"tag":188,"props":658,"children":659},{"style":226},[660],{"type":51,"value":593},{"type":38,"tag":188,"props":662,"children":663},{"class":190,"line":477},[664,669],{"type":38,"tag":188,"props":665,"children":666},{"style":310},[667],{"type":51,"value":668},"      \"value\"",{"type":38,"tag":188,"props":670,"children":671},{"style":226},[672],{"type":51,"value":673},": {\n",{"type":38,"tag":188,"props":675,"children":676},{"class":190,"line":505},[677,682,686,691],{"type":38,"tag":188,"props":678,"children":679},{"style":310},[680],{"type":51,"value":681},"        \"messaging_product\"",{"type":38,"tag":188,"props":683,"children":684},{"style":226},[685],{"type":51,"value":307},{"type":38,"tag":188,"props":687,"children":688},{"style":243},[689],{"type":51,"value":690},"\"whatsapp\"",{"type":38,"tag":188,"props":692,"children":693},{"style":226},[694],{"type":51,"value":593},{"type":38,"tag":188,"props":696,"children":698},{"class":190,"line":697},9,[699,704,709,714,718,723,727,732,736,741],{"type":38,"tag":188,"props":700,"children":701},{"style":310},[702],{"type":51,"value":703},"        \"metadata\"",{"type":38,"tag":188,"props":705,"children":706},{"style":226},[707],{"type":51,"value":708},": { ",{"type":38,"tag":188,"props":710,"children":711},{"style":310},[712],{"type":51,"value":713},"\"display_phone_number\"",{"type":38,"tag":188,"props":715,"children":716},{"style":226},[717],{"type":51,"value":307},{"type":38,"tag":188,"props":719,"children":720},{"style":243},[721],{"type":51,"value":722},"\"15550001111\"",{"type":38,"tag":188,"props":724,"children":725},{"style":226},[726],{"type":51,"value":262},{"type":38,"tag":188,"props":728,"children":729},{"style":310},[730],{"type":51,"value":731},"\"phone_number_id\"",{"type":38,"tag":188,"props":733,"children":734},{"style":226},[735],{"type":51,"value":307},{"type":38,"tag":188,"props":737,"children":738},{"style":243},[739],{"type":51,"value":740},"\"PHONE_ID\"",{"type":38,"tag":188,"props":742,"children":743},{"style":226},[744],{"type":51,"value":745}," },\n",{"type":38,"tag":188,"props":747,"children":749},{"class":190,"line":748},10,[750,755,760,765,769,774,778,783,788,793,797,802],{"type":38,"tag":188,"props":751,"children":752},{"style":310},[753],{"type":51,"value":754},"        \"contacts\"",{"type":38,"tag":188,"props":756,"children":757},{"style":226},[758],{"type":51,"value":759},": [{ ",{"type":38,"tag":188,"props":761,"children":762},{"style":310},[763],{"type":51,"value":764},"\"profile\"",{"type":38,"tag":188,"props":766,"children":767},{"style":226},[768],{"type":51,"value":708},{"type":38,"tag":188,"props":770,"children":771},{"style":310},[772],{"type":51,"value":773},"\"name\"",{"type":38,"tag":188,"props":775,"children":776},{"style":226},[777],{"type":51,"value":307},{"type":38,"tag":188,"props":779,"children":780},{"style":243},[781],{"type":51,"value":782},"\"Alex Doe\"",{"type":38,"tag":188,"props":784,"children":785},{"style":226},[786],{"type":51,"value":787}," }, ",{"type":38,"tag":188,"props":789,"children":790},{"style":310},[791],{"type":51,"value":792},"\"wa_id\"",{"type":38,"tag":188,"props":794,"children":795},{"style":226},[796],{"type":51,"value":307},{"type":38,"tag":188,"props":798,"children":799},{"style":243},[800],{"type":51,"value":801},"\"15557770000\"",{"type":38,"tag":188,"props":803,"children":804},{"style":226},[805],{"type":51,"value":806}," }],\n",{"type":38,"tag":188,"props":808,"children":810},{"class":190,"line":809},11,[811,816],{"type":38,"tag":188,"props":812,"children":813},{"style":310},[814],{"type":51,"value":815},"        \"messages\"",{"type":38,"tag":188,"props":817,"children":818},{"style":226},[819],{"type":51,"value":606},{"type":38,"tag":188,"props":821,"children":823},{"class":190,"line":822},12,[824,829,833,837],{"type":38,"tag":188,"props":825,"children":826},{"style":310},[827],{"type":51,"value":828},"          \"from\"",{"type":38,"tag":188,"props":830,"children":831},{"style":226},[832],{"type":51,"value":307},{"type":38,"tag":188,"props":834,"children":835},{"style":243},[836],{"type":51,"value":801},{"type":38,"tag":188,"props":838,"children":839},{"style":226},[840],{"type":51,"value":593},{"type":38,"tag":188,"props":842,"children":844},{"class":190,"line":843},13,[845,850,854,859],{"type":38,"tag":188,"props":846,"children":847},{"style":310},[848],{"type":51,"value":849},"          \"id\"",{"type":38,"tag":188,"props":851,"children":852},{"style":226},[853],{"type":51,"value":307},{"type":38,"tag":188,"props":855,"children":856},{"style":243},[857],{"type":51,"value":858},"\"wamid.HBgL...\"",{"type":38,"tag":188,"props":860,"children":861},{"style":226},[862],{"type":51,"value":593},{"type":38,"tag":188,"props":864,"children":866},{"class":190,"line":865},14,[867,872,876,881],{"type":38,"tag":188,"props":868,"children":869},{"style":310},[870],{"type":51,"value":871},"          \"timestamp\"",{"type":38,"tag":188,"props":873,"children":874},{"style":226},[875],{"type":51,"value":307},{"type":38,"tag":188,"props":877,"children":878},{"style":243},[879],{"type":51,"value":880},"\"1768472000\"",{"type":38,"tag":188,"props":882,"children":883},{"style":226},[884],{"type":51,"value":593},{"type":38,"tag":188,"props":886,"children":888},{"class":190,"line":887},15,[889,894,898,903],{"type":38,"tag":188,"props":890,"children":891},{"style":310},[892],{"type":51,"value":893},"          \"type\"",{"type":38,"tag":188,"props":895,"children":896},{"style":226},[897],{"type":51,"value":307},{"type":38,"tag":188,"props":899,"children":900},{"style":243},[901],{"type":51,"value":902},"\"text\"",{"type":38,"tag":188,"props":904,"children":905},{"style":226},[906],{"type":51,"value":593},{"type":38,"tag":188,"props":908,"children":910},{"class":190,"line":909},16,[911,916,920,925,929,934],{"type":38,"tag":188,"props":912,"children":913},{"style":310},[914],{"type":51,"value":915},"          \"text\"",{"type":38,"tag":188,"props":917,"children":918},{"style":226},[919],{"type":51,"value":708},{"type":38,"tag":188,"props":921,"children":922},{"style":310},[923],{"type":51,"value":924},"\"body\"",{"type":38,"tag":188,"props":926,"children":927},{"style":226},[928],{"type":51,"value":307},{"type":38,"tag":188,"props":930,"children":931},{"style":243},[932],{"type":51,"value":933},"\"hello!\"",{"type":38,"tag":188,"props":935,"children":936},{"style":226},[937],{"type":51,"value":938}," }\n",{"type":38,"tag":188,"props":940,"children":942},{"class":190,"line":941},17,[943],{"type":38,"tag":188,"props":944,"children":945},{"style":226},[946],{"type":51,"value":947},"        }]\n",{"type":38,"tag":188,"props":949,"children":951},{"class":190,"line":950},18,[952],{"type":38,"tag":188,"props":953,"children":954},{"style":226},[955],{"type":51,"value":956},"      }\n",{"type":38,"tag":188,"props":958,"children":960},{"class":190,"line":959},19,[961],{"type":38,"tag":188,"props":962,"children":963},{"style":226},[964],{"type":51,"value":965},"    }]\n",{"type":38,"tag":188,"props":967,"children":969},{"class":190,"line":968},20,[970],{"type":38,"tag":188,"props":971,"children":972},{"style":226},[973],{"type":51,"value":974},"  }]\n",{"type":38,"tag":188,"props":976,"children":978},{"class":190,"line":977},21,[979],{"type":38,"tag":188,"props":980,"children":981},{"style":226},[982],{"type":51,"value":983},"}\n",{"type":38,"tag":39,"props":985,"children":986},{},[987,989,995,997,1002,1004,1010,1012,1018],{"type":51,"value":988},"Status updates (sent/delivered/read) arrive on the same URL with a ",{"type":38,"tag":103,"props":990,"children":992},{"className":991},[],[993],{"type":51,"value":994},"statuses",{"type":51,"value":996}," array instead of ",{"type":38,"tag":103,"props":998,"children":1000},{"className":999},[],[1001],{"type":51,"value":108},{"type":51,"value":1003},". Your handler should switch on what is present — and remember ",{"type":38,"tag":103,"props":1005,"children":1007},{"className":1006},[],[1008],{"type":51,"value":1009},"entry",{"type":51,"value":1011}," and ",{"type":38,"tag":103,"props":1013,"children":1015},{"className":1014},[],[1016],{"type":51,"value":1017},"changes",{"type":51,"value":1019}," are arrays; batches happen.",{"type":38,"tag":39,"props":1021,"children":1022},{},[1023,1025,1031,1033,1039],{"type":51,"value":1024},"The fastest way to learn the shapes is to look at real traffic: point the callback URL at a free ",{"type":38,"tag":143,"props":1026,"children":1028},{"href":1027},"/webhook-bin",[1029],{"type":51,"value":1030},"Webhook Bin",{"type":51,"value":1032}," for ten minutes of testing (it answers the GET handshake's ",{"type":38,"tag":103,"props":1034,"children":1036},{"className":1035},[],[1037],{"type":51,"value":1038},"hub.challenge",{"type":51,"value":1040}," echo requirement if you configure the bin response, or verify against your real endpoint first and then switch) and message your test number.",{"type":38,"tag":69,"props":1042,"children":1044},{"id":1043},"step-3-verify-the-signature",[1045],{"type":51,"value":1046},"Step 3: Verify the signature",{"type":38,"tag":39,"props":1048,"children":1049},{},[1050,1052,1058,1060,1065,1067,1072,1074,1080,1082,1088],{"type":51,"value":1051},"Every POST carries ",{"type":38,"tag":103,"props":1053,"children":1055},{"className":1054},[],[1056],{"type":51,"value":1057},"X-Hub-Signature-256: sha256=\u003Chex>",{"type":51,"value":1059}," — HMAC-SHA256 of the ",{"type":38,"tag":54,"props":1061,"children":1062},{},[1063],{"type":51,"value":1064},"raw body",{"type":51,"value":1066}," keyed with your ",{"type":38,"tag":54,"props":1068,"children":1069},{},[1070],{"type":51,"value":1071},"app secret",{"type":51,"value":1073}," (Dashboard → App settings → Basic). Recompute and compare in constant time; the mechanics and language snippets are identical to GitHub's scheme, covered in ",{"type":38,"tag":143,"props":1075,"children":1077},{"href":1076},"/blog/verify-webhook-signature",[1078],{"type":51,"value":1079},"Verify a webhook signature",{"type":51,"value":1081},", and you can sanity-check values in the free ",{"type":38,"tag":143,"props":1083,"children":1085},{"href":1084},"/hmac-verification",[1086],{"type":51,"value":1087},"HMAC verifier",{"type":51,"value":110},{"type":38,"tag":69,"props":1090,"children":1092},{"id":1091},"self-hosted-gateways-waha-evolution-api-and-friends",[1093],{"type":51,"value":1094},"Self-hosted gateways: WAHA, Evolution API and friends",{"type":38,"tag":39,"props":1096,"children":1097},{},[1098,1100,1105,1106,1111,1112,1117,1119,1124,1126,1131],{"type":51,"value":1099},"Not every WhatsApp integration uses Meta's hosted Cloud API. Self-hosted gateways — ",{"type":38,"tag":54,"props":1101,"children":1102},{},[1103],{"type":51,"value":1104},"WAHA",{"type":51,"value":262},{"type":38,"tag":54,"props":1107,"children":1108},{},[1109],{"type":51,"value":1110},"Evolution API",{"type":51,"value":262},{"type":38,"tag":54,"props":1113,"children":1114},{},[1115],{"type":51,"value":1116},"uazapi",{"type":51,"value":1118}," and similar — run a WhatsApp Web session in a container and forward incoming messages to ",{"type":38,"tag":88,"props":1120,"children":1121},{},[1122],{"type":51,"value":1123},"your",{"type":51,"value":1125}," webhook URL with much simpler payloads (we see plenty of WAHA traffic in our own ",{"type":38,"tag":143,"props":1127,"children":1128},{"href":1027},[1129],{"type":51,"value":1130},"webhook bin",{"type":51,"value":1132},"). The receiving patterns in this guide apply unchanged: inspect the gateway's payload in a bin first, then point it at your stable endpoint. No verify-token handshake, but also no Meta signature — check what auth headers your gateway can send and require them.",{"type":38,"tag":69,"props":1134,"children":1136},{"id":1135},"local-development-without-re-verifying",[1137],{"type":51,"value":1138},"Local development without re-verifying",{"type":38,"tag":39,"props":1140,"children":1141},{},[1142,1144,1149],{"type":51,"value":1143},"Meta's URL verification makes tunnels painful: every time your tunnel URL changes, you re-run the handshake. The fix is a ",{"type":38,"tag":54,"props":1145,"children":1146},{},[1147],{"type":51,"value":1148},"stable",{"type":51,"value":1150}," public endpoint that never changes, relayed to wherever your code currently runs:",{"type":38,"tag":1152,"props":1153,"children":1154},"ol",{},[1155,1161,1172],{"type":38,"tag":1156,"props":1157,"children":1158},"li",{},[1159],{"type":51,"value":1160},"Create a Webhook Relay bucket — its input URL is permanent.",{"type":38,"tag":1156,"props":1162,"children":1163},{},[1164,1166,1171],{"type":51,"value":1165},"Verify that URL with Meta ",{"type":38,"tag":54,"props":1167,"children":1168},{},[1169],{"type":51,"value":1170},"once",{"type":51,"value":110},{"type":38,"tag":1156,"props":1173,"children":1174},{},[1175,1177,1183],{"type":51,"value":1176},"Forward to your machine: ",{"type":38,"tag":103,"props":1178,"children":1180},{"className":1179},[],[1181],{"type":51,"value":1182},"relay forward --bucket whatsapp http://localhost:3000/webhook",{"type":51,"value":110},{"type":38,"tag":39,"props":1185,"children":1186},{},[1187,1189,1195,1197,1203],{"type":51,"value":1188},"Your laptop connects outbound, so no ports open, and restarting or moving your dev environment never touches the Meta configuration. The full walkthrough is in ",{"type":38,"tag":143,"props":1190,"children":1192},{"href":1191},"/blog/receive-whatsapp-webhooks-locally",[1193],{"type":51,"value":1194},"Test WhatsApp webhooks locally",{"type":51,"value":1196},". Building the automation in n8n instead of code? See the ",{"type":38,"tag":143,"props":1198,"children":1200},{"href":1199},"/docs/tutorials/n8n/whatsapp-cloud-api-webhook",[1201],{"type":51,"value":1202},"n8n WhatsApp Cloud API webhook tutorial",{"type":51,"value":110},{"type":38,"tag":69,"props":1205,"children":1207},{"id":1206},"related-reading",[1208],{"type":51,"value":1209},"Related reading",{"type":38,"tag":1211,"props":1212,"children":1213},"ul",{},[1214,1221,1229,1236],{"type":38,"tag":1156,"props":1215,"children":1216},{},[1217],{"type":38,"tag":143,"props":1218,"children":1219},{"href":1191},[1220],{"type":51,"value":1194},{"type":38,"tag":1156,"props":1222,"children":1223},{},[1224],{"type":38,"tag":143,"props":1225,"children":1226},{"href":1199},[1227],{"type":51,"value":1228},"n8n WhatsApp Cloud API webhook trigger",{"type":38,"tag":1156,"props":1230,"children":1231},{},[1232],{"type":38,"tag":143,"props":1233,"children":1234},{"href":1076},[1235],{"type":51,"value":1079},{"type":38,"tag":1156,"props":1237,"children":1238},{},[1239],{"type":38,"tag":143,"props":1240,"children":1242},{"href":1241},"/blog/how-to-test-webhooks",[1243],{"type":51,"value":1244},"How to test webhooks",{"type":38,"tag":1246,"props":1247,"children":1248},"style",{},[1249],{"type":51,"value":1250},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":286,"depth":286,"links":1252},[1253,1254,1255,1256,1257,1258,1259],{"id":71,"depth":222,"text":74},{"id":133,"depth":222,"text":136},{"id":546,"depth":222,"text":549},{"id":1043,"depth":222,"text":1046},{"id":1091,"depth":222,"text":1094},{"id":1135,"depth":222,"text":1138},{"id":1206,"depth":222,"text":1209},"markdown","content:blog:whatsapp-cloud-api-webhooks.md","content","blog/whatsapp-cloud-api-webhooks.md","blog/whatsapp-cloud-api-webhooks","md",{"loc":4},1783373260875]