[{"data":1,"prerenderedAt":1311},["ShallowReactive",2],{"content-query-dk20qgbRS8":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"layout":10,"head":11,"author":13,"tags":14,"categories":20,"date":22,"image":23,"faq":24,"excerpt":34,"body":83,"_type":1304,"_id":1305,"_source":1306,"_file":1307,"_stem":1308,"_extension":1309,"sitemap":1310},"/blog/microsoft-graph-webhook-validation","blog",false,"","Microsoft Graph Webhook Validation: Return validationToken","Pass the Microsoft Graph webhook validation handshake by returning validationToken as text/plain, then verify clientState and forward change notifications.","post",{"title":12},"Microsoft Graph Webhook Validation with validationToken","Karolis Rusenas",[15,16,17,18,19],"microsoft graph","webhooks","validation","javascript","microsoft 365",[21],"integrations","2026-09-06 10:30:00","/images/blog/heroes/route.jpg",[25,28,31],{"q":26,"a":27},"How does Microsoft Graph validate a webhook URL?","Graph sends a POST to the notification URL with validationToken in the query string. The endpoint must URL-decode it and return the exact token as a text/plain response with HTTP 200 within ten seconds.",{"q":29,"a":30},"Why does my Microsoft Graph subscription creation fail?","The usual causes are returning JSON instead of plain text, not URL-decoding validationToken, taking more than ten seconds, redirecting the notification URL, or attaching the response function to the wrong input.",{"q":32,"a":33},"How do I authenticate Microsoft Graph change notifications?","Set a random clientState when creating the subscription and compare it with clientState in each notification before processing the event. Also use HTTPS and follow Microsoft's validation-token guidance for richer notification payloads.",{"type":35,"children":36},"root",[37,70],{"type":38,"tag":39,"props":40,"children":41},"element","p",{},[42,45,52,54,60,62,68],{"type":43,"value":44},"text","Microsoft Graph validates every webhook notification URL before it creates a subscription. It sends an HTTP ",{"type":38,"tag":46,"props":47,"children":49},"code",{"className":48},[],[50],{"type":43,"value":51},"POST",{"type":43,"value":53}," with ",{"type":38,"tag":46,"props":55,"children":57},{"className":56},[],[58],{"type":43,"value":59},"validationToken",{"type":43,"value":61}," in the query string, and your endpoint must return the URL-decoded token as the complete ",{"type":38,"tag":46,"props":63,"children":65},{"className":64},[],[66],{"type":43,"value":67},"text/plain",{"type":43,"value":69}," response within ten seconds.",{"type":38,"tag":39,"props":71,"children":72},{},[73,75,81],{"type":43,"value":74},"An ordinary webhook receiver that returns ",{"type":38,"tag":46,"props":76,"children":78},{"className":77},[],[79],{"type":43,"value":80},"{ \"ok\": true }",{"type":43,"value":82}," will fail this handshake. A Webhook Relay input function can answer it at the public edge while forwarding real Microsoft Graph change notifications to your public or private application.",{"type":35,"children":84,"toc":1287},[85,107,117,124,129,149,154,197,234,240,245,283,288,301,314,506,517,522,528,533,660,673,692,698,718,1071,1084,1089,1095,1116,1121,1127,1140,1152,1158,1191,1197,1217,1223,1235,1241,1253,1281],{"type":38,"tag":39,"props":86,"children":87},{},[88,89,94,95,100,101,106],{"type":43,"value":44},{"type":38,"tag":46,"props":90,"children":92},{"className":91},[],[93],{"type":43,"value":51},{"type":43,"value":53},{"type":38,"tag":46,"props":96,"children":98},{"className":97},[],[99],{"type":43,"value":59},{"type":43,"value":61},{"type":38,"tag":46,"props":102,"children":104},{"className":103},[],[105],{"type":43,"value":67},{"type":43,"value":69},{"type":38,"tag":39,"props":108,"children":109},{},[110,111,116],{"type":43,"value":74},{"type":38,"tag":46,"props":112,"children":114},{"className":113},[],[115],{"type":43,"value":80},{"type":43,"value":82},{"type":38,"tag":118,"props":119,"children":121},"h2",{"id":120},"the-microsoft-graph-validation-request",[122],{"type":43,"value":123},"The Microsoft Graph validation request",{"type":38,"tag":39,"props":125,"children":126},{},[127],{"type":43,"value":128},"The request looks like this:",{"type":38,"tag":130,"props":131,"children":135},"pre",{"className":132,"code":133,"language":134,"meta":7,"style":7},"language-http shiki shiki-themes github-dark","POST /your-webhook?validationToken=Validation%3A+Testing+client+application HTTP/1.1\n","http",[136],{"type":38,"tag":46,"props":137,"children":138},{"__ignoreMap":7},[139],{"type":38,"tag":140,"props":141,"children":144},"span",{"class":142,"line":143},"line",1,[145],{"type":38,"tag":140,"props":146,"children":147},{},[148],{"type":43,"value":133},{"type":38,"tag":39,"props":150,"children":151},{},[152],{"type":43,"value":153},"The successful response must look like:",{"type":38,"tag":130,"props":155,"children":157},{"className":132,"code":156,"language":134,"meta":7,"style":7},"HTTP/1.1 200 OK\nContent-Type: text/plain\n\nValidation: Testing client application\n",[158],{"type":38,"tag":46,"props":159,"children":160},{"__ignoreMap":7},[161,169,178,188],{"type":38,"tag":140,"props":162,"children":163},{"class":142,"line":143},[164],{"type":38,"tag":140,"props":165,"children":166},{},[167],{"type":43,"value":168},"HTTP/1.1 200 OK\n",{"type":38,"tag":140,"props":170,"children":172},{"class":142,"line":171},2,[173],{"type":38,"tag":140,"props":174,"children":175},{},[176],{"type":43,"value":177},"Content-Type: text/plain\n",{"type":38,"tag":140,"props":179,"children":181},{"class":142,"line":180},3,[182],{"type":38,"tag":140,"props":183,"children":185},{"emptyLinePlaceholder":184},true,[186],{"type":43,"value":187},"\n",{"type":38,"tag":140,"props":189,"children":191},{"class":142,"line":190},4,[192],{"type":38,"tag":140,"props":193,"children":194},{},[195],{"type":43,"value":196},"Validation: Testing client application\n",{"type":38,"tag":39,"props":198,"children":199},{},[200,202,211,213,218,220,226,227,232],{"type":43,"value":201},"Microsoft's ",{"type":38,"tag":203,"props":204,"children":208},"a",{"href":205,"rel":206},"https://learn.microsoft.com/en-us/graph/change-notifications-delivery-webhooks",[207],"nofollow",[209],{"type":43,"value":210},"change-notification delivery documentation",{"type":43,"value":212}," specifies the ",{"type":38,"tag":46,"props":214,"children":216},{"className":215},[],[217],{"type":43,"value":51},{"type":43,"value":219},", ",{"type":38,"tag":46,"props":221,"children":223},{"className":222},[],[224],{"type":43,"value":225},"200",{"type":43,"value":219},{"type":38,"tag":46,"props":228,"children":230},{"className":229},[],[231],{"type":43,"value":67},{"type":43,"value":233},", decoded token and ten-second deadline. Treat the token as opaque text. Do not wrap it in JSON, add quotes, or return the encoded query value.",{"type":38,"tag":118,"props":235,"children":237},{"id":236},"_1-create-the-public-input",[238],{"type":43,"value":239},"1. Create the public input",{"type":38,"tag":39,"props":241,"children":242},{},[243],{"type":43,"value":244},"Create a Webhook Relay bucket and copy its HTTPS input URL. Add your application endpoint as an output:",{"type":38,"tag":246,"props":247,"children":248},"ul",{},[249,263],{"type":38,"tag":250,"props":251,"children":252},"li",{},[253,255,261],{"type":43,"value":254},"For a public API, use a ",{"type":38,"tag":203,"props":256,"children":258},{"href":257},"/docs/webhooks/public/public-destination",[259],{"type":43,"value":260},"public destination",{"type":43,"value":262},".",{"type":38,"tag":250,"props":264,"children":265},{},[266,268,274,276,282],{"type":43,"value":267},"For ",{"type":38,"tag":46,"props":269,"children":271},{"className":270},[],[272],{"type":43,"value":273},"localhost",{"type":43,"value":275}," or a private service, use the ",{"type":38,"tag":203,"props":277,"children":279},{"href":278},"/docs/webhooks/internal/localhost",[280],{"type":43,"value":281},"relay agent",{"type":43,"value":262},{"type":38,"tag":39,"props":284,"children":285},{},[286],{"type":43,"value":287},"The same stable input URL handles both the one-time validation request and later notifications.",{"type":38,"tag":118,"props":289,"children":291},{"id":290},"_2-answer-validationtoken-in-a-javascript-function",[292,294,299],{"type":43,"value":293},"2. Answer ",{"type":38,"tag":46,"props":295,"children":297},{"className":296},[],[298],{"type":43,"value":59},{"type":43,"value":300}," in a JavaScript function",{"type":38,"tag":39,"props":302,"children":303},{},[304,306,312],{"type":43,"value":305},"Create a function and attach it to the bucket ",{"type":38,"tag":307,"props":308,"children":309},"strong",{},[310],{"type":43,"value":311},"input",{"type":43,"value":313},", not the output:",{"type":38,"tag":130,"props":315,"children":318},{"className":316,"code":317,"language":18,"meta":7,"style":7},"language-javascript shiki shiki-themes github-dark","const validationToken = r.query.validationToken\n\nif (r.method === \"POST\" && validationToken) {\n  r.setResponseStatus(200)\n  r.setResponseHeader(\"Content-Type\", \"text/plain\")\n  r.setResponseBody(validationToken)\n  r.stopForwarding()\n  return\n}\n",[319],{"type":38,"tag":46,"props":320,"children":321},{"__ignoreMap":7},[322,348,355,389,417,452,470,488,497],{"type":38,"tag":140,"props":323,"children":324},{"class":142,"line":143},[325,331,337,342],{"type":38,"tag":140,"props":326,"children":328},{"style":327},"--shiki-default:#F97583",[329],{"type":43,"value":330},"const",{"type":38,"tag":140,"props":332,"children":334},{"style":333},"--shiki-default:#79B8FF",[335],{"type":43,"value":336}," validationToken",{"type":38,"tag":140,"props":338,"children":339},{"style":327},[340],{"type":43,"value":341}," =",{"type":38,"tag":140,"props":343,"children":345},{"style":344},"--shiki-default:#E1E4E8",[346],{"type":43,"value":347}," r.query.validationToken\n",{"type":38,"tag":140,"props":349,"children":350},{"class":142,"line":171},[351],{"type":38,"tag":140,"props":352,"children":353},{"emptyLinePlaceholder":184},[354],{"type":43,"value":187},{"type":38,"tag":140,"props":356,"children":357},{"class":142,"line":180},[358,363,368,373,379,384],{"type":38,"tag":140,"props":359,"children":360},{"style":327},[361],{"type":43,"value":362},"if",{"type":38,"tag":140,"props":364,"children":365},{"style":344},[366],{"type":43,"value":367}," (r.method ",{"type":38,"tag":140,"props":369,"children":370},{"style":327},[371],{"type":43,"value":372},"===",{"type":38,"tag":140,"props":374,"children":376},{"style":375},"--shiki-default:#9ECBFF",[377],{"type":43,"value":378}," \"POST\"",{"type":38,"tag":140,"props":380,"children":381},{"style":327},[382],{"type":43,"value":383}," &&",{"type":38,"tag":140,"props":385,"children":386},{"style":344},[387],{"type":43,"value":388}," validationToken) {\n",{"type":38,"tag":140,"props":390,"children":391},{"class":142,"line":190},[392,397,403,408,412],{"type":38,"tag":140,"props":393,"children":394},{"style":344},[395],{"type":43,"value":396},"  r.",{"type":38,"tag":140,"props":398,"children":400},{"style":399},"--shiki-default:#B392F0",[401],{"type":43,"value":402},"setResponseStatus",{"type":38,"tag":140,"props":404,"children":405},{"style":344},[406],{"type":43,"value":407},"(",{"type":38,"tag":140,"props":409,"children":410},{"style":333},[411],{"type":43,"value":225},{"type":38,"tag":140,"props":413,"children":414},{"style":344},[415],{"type":43,"value":416},")\n",{"type":38,"tag":140,"props":418,"children":420},{"class":142,"line":419},5,[421,425,430,434,439,443,448],{"type":38,"tag":140,"props":422,"children":423},{"style":344},[424],{"type":43,"value":396},{"type":38,"tag":140,"props":426,"children":427},{"style":399},[428],{"type":43,"value":429},"setResponseHeader",{"type":38,"tag":140,"props":431,"children":432},{"style":344},[433],{"type":43,"value":407},{"type":38,"tag":140,"props":435,"children":436},{"style":375},[437],{"type":43,"value":438},"\"Content-Type\"",{"type":38,"tag":140,"props":440,"children":441},{"style":344},[442],{"type":43,"value":219},{"type":38,"tag":140,"props":444,"children":445},{"style":375},[446],{"type":43,"value":447},"\"text/plain\"",{"type":38,"tag":140,"props":449,"children":450},{"style":344},[451],{"type":43,"value":416},{"type":38,"tag":140,"props":453,"children":455},{"class":142,"line":454},6,[456,460,465],{"type":38,"tag":140,"props":457,"children":458},{"style":344},[459],{"type":43,"value":396},{"type":38,"tag":140,"props":461,"children":462},{"style":399},[463],{"type":43,"value":464},"setResponseBody",{"type":38,"tag":140,"props":466,"children":467},{"style":344},[468],{"type":43,"value":469},"(validationToken)\n",{"type":38,"tag":140,"props":471,"children":473},{"class":142,"line":472},7,[474,478,483],{"type":38,"tag":140,"props":475,"children":476},{"style":344},[477],{"type":43,"value":396},{"type":38,"tag":140,"props":479,"children":480},{"style":399},[481],{"type":43,"value":482},"stopForwarding",{"type":38,"tag":140,"props":484,"children":485},{"style":344},[486],{"type":43,"value":487},"()\n",{"type":38,"tag":140,"props":489,"children":491},{"class":142,"line":490},8,[492],{"type":38,"tag":140,"props":493,"children":494},{"style":327},[495],{"type":43,"value":496},"  return\n",{"type":38,"tag":140,"props":498,"children":500},{"class":142,"line":499},9,[501],{"type":38,"tag":140,"props":502,"children":503},{"style":344},[504],{"type":43,"value":505},"}\n",{"type":38,"tag":39,"props":507,"children":508},{},[509,515],{"type":38,"tag":46,"props":510,"children":512},{"className":511},[],[513],{"type":43,"value":514},"r.query.validationToken",{"type":43,"value":516}," is the parsed, URL-decoded query value. The function returns it directly and stops the validation request from reaching the downstream application. Requests without the token continue to the normal outputs.",{"type":38,"tag":39,"props":518,"children":519},{},[520],{"type":43,"value":521},"Do not use the validation token as a persistent secret. It proves that Microsoft Graph can reach and read the callback response; it is not the value used to authenticate subsequent notifications.",{"type":38,"tag":118,"props":523,"children":525},{"id":524},"_3-create-the-graph-subscription",[526],{"type":43,"value":527},"3. Create the Graph subscription",{"type":38,"tag":39,"props":529,"children":530},{},[531],{"type":43,"value":532},"A subscription request varies by resource, but the key webhook fields look like this:",{"type":38,"tag":130,"props":534,"children":538},{"className":535,"code":536,"language":537,"meta":7,"style":7},"language-json shiki shiki-themes github-dark","{\n  \"changeType\": \"created,updated\",\n  \"notificationUrl\": \"https://example.hooks.webhookrelay.com\",\n  \"resource\": \"/users/{user-id}/messages\",\n  \"expirationDateTime\": \"2026-09-07T10:00:00Z\",\n  \"clientState\": \"a-random-secret-value\"\n}\n","json",[539],{"type":38,"tag":46,"props":540,"children":541},{"__ignoreMap":7},[542,550,573,594,615,636,653],{"type":38,"tag":140,"props":543,"children":544},{"class":142,"line":143},[545],{"type":38,"tag":140,"props":546,"children":547},{"style":344},[548],{"type":43,"value":549},"{\n",{"type":38,"tag":140,"props":551,"children":552},{"class":142,"line":171},[553,558,563,568],{"type":38,"tag":140,"props":554,"children":555},{"style":333},[556],{"type":43,"value":557},"  \"changeType\"",{"type":38,"tag":140,"props":559,"children":560},{"style":344},[561],{"type":43,"value":562},": ",{"type":38,"tag":140,"props":564,"children":565},{"style":375},[566],{"type":43,"value":567},"\"created,updated\"",{"type":38,"tag":140,"props":569,"children":570},{"style":344},[571],{"type":43,"value":572},",\n",{"type":38,"tag":140,"props":574,"children":575},{"class":142,"line":180},[576,581,585,590],{"type":38,"tag":140,"props":577,"children":578},{"style":333},[579],{"type":43,"value":580},"  \"notificationUrl\"",{"type":38,"tag":140,"props":582,"children":583},{"style":344},[584],{"type":43,"value":562},{"type":38,"tag":140,"props":586,"children":587},{"style":375},[588],{"type":43,"value":589},"\"https://example.hooks.webhookrelay.com\"",{"type":38,"tag":140,"props":591,"children":592},{"style":344},[593],{"type":43,"value":572},{"type":38,"tag":140,"props":595,"children":596},{"class":142,"line":190},[597,602,606,611],{"type":38,"tag":140,"props":598,"children":599},{"style":333},[600],{"type":43,"value":601},"  \"resource\"",{"type":38,"tag":140,"props":603,"children":604},{"style":344},[605],{"type":43,"value":562},{"type":38,"tag":140,"props":607,"children":608},{"style":375},[609],{"type":43,"value":610},"\"/users/{user-id}/messages\"",{"type":38,"tag":140,"props":612,"children":613},{"style":344},[614],{"type":43,"value":572},{"type":38,"tag":140,"props":616,"children":617},{"class":142,"line":419},[618,623,627,632],{"type":38,"tag":140,"props":619,"children":620},{"style":333},[621],{"type":43,"value":622},"  \"expirationDateTime\"",{"type":38,"tag":140,"props":624,"children":625},{"style":344},[626],{"type":43,"value":562},{"type":38,"tag":140,"props":628,"children":629},{"style":375},[630],{"type":43,"value":631},"\"2026-09-07T10:00:00Z\"",{"type":38,"tag":140,"props":633,"children":634},{"style":344},[635],{"type":43,"value":572},{"type":38,"tag":140,"props":637,"children":638},{"class":142,"line":454},[639,644,648],{"type":38,"tag":140,"props":640,"children":641},{"style":333},[642],{"type":43,"value":643},"  \"clientState\"",{"type":38,"tag":140,"props":645,"children":646},{"style":344},[647],{"type":43,"value":562},{"type":38,"tag":140,"props":649,"children":650},{"style":375},[651],{"type":43,"value":652},"\"a-random-secret-value\"\n",{"type":38,"tag":140,"props":654,"children":655},{"class":142,"line":472},[656],{"type":38,"tag":140,"props":657,"children":658},{"style":344},[659],{"type":43,"value":505},{"type":38,"tag":39,"props":661,"children":662},{},[663,665,671],{"type":43,"value":664},"Use an expiration time supported by the selected resource and renew the subscription before it expires. Store ",{"type":38,"tag":46,"props":666,"children":668},{"className":667},[],[669],{"type":43,"value":670},"clientState",{"type":43,"value":672}," as a secret generated for this subscription.",{"type":38,"tag":39,"props":674,"children":675},{},[676,678,683,685,690],{"type":43,"value":677},"When Graph creates the subscription, open the Webhook Relay request log. You should see the validation ",{"type":38,"tag":46,"props":679,"children":681},{"className":680},[],[682],{"type":43,"value":51},{"type":43,"value":684}," with a ",{"type":38,"tag":46,"props":686,"children":688},{"className":687},[],[689],{"type":43,"value":225},{"type":43,"value":691}," plain-text response. If subscription creation fails, compare the recorded response body byte-for-byte with the decoded query value.",{"type":38,"tag":118,"props":693,"children":695},{"id":694},"_4-validate-real-change-notifications",[696],{"type":43,"value":697},"4. Validate real change notifications",{"type":38,"tag":39,"props":699,"children":700},{},[701,703,709,711,716],{"type":43,"value":702},"The notification body contains a ",{"type":38,"tag":46,"props":704,"children":706},{"className":705},[],[707],{"type":43,"value":708},"value",{"type":43,"value":710}," array. Each notification should have the ",{"type":38,"tag":46,"props":712,"children":714},{"className":713},[],[715],{"type":43,"value":670},{"type":43,"value":717}," you supplied during subscription creation:",{"type":38,"tag":130,"props":719,"children":721},{"className":316,"code":720,"language":18,"meta":7,"style":7},"const payload = JSON.parse(r.body)\nconst expectedClientState = cfg.get(\"GRAPH_CLIENT_STATE\")\nconst notifications = Array.isArray(payload.value) ? payload.value : []\n\nconst valid = notifications.length > 0 && notifications.every(function (item) {\n  return item.clientState === expectedClientState\n})\n\nif (!valid) {\n  r.setResponseStatus(403)\n  r.setResponseBody(\"invalid clientState\")\n  r.stopForwarding()\n  return\n}\n",[722],{"type":38,"tag":46,"props":723,"children":724},{"__ignoreMap":7},[725,760,799,850,857,931,953,961,968,989,1014,1039,1055,1063],{"type":38,"tag":140,"props":726,"children":727},{"class":142,"line":143},[728,732,737,741,746,750,755],{"type":38,"tag":140,"props":729,"children":730},{"style":327},[731],{"type":43,"value":330},{"type":38,"tag":140,"props":733,"children":734},{"style":333},[735],{"type":43,"value":736}," payload",{"type":38,"tag":140,"props":738,"children":739},{"style":327},[740],{"type":43,"value":341},{"type":38,"tag":140,"props":742,"children":743},{"style":333},[744],{"type":43,"value":745}," JSON",{"type":38,"tag":140,"props":747,"children":748},{"style":344},[749],{"type":43,"value":262},{"type":38,"tag":140,"props":751,"children":752},{"style":399},[753],{"type":43,"value":754},"parse",{"type":38,"tag":140,"props":756,"children":757},{"style":344},[758],{"type":43,"value":759},"(r.body)\n",{"type":38,"tag":140,"props":761,"children":762},{"class":142,"line":171},[763,767,772,776,781,786,790,795],{"type":38,"tag":140,"props":764,"children":765},{"style":327},[766],{"type":43,"value":330},{"type":38,"tag":140,"props":768,"children":769},{"style":333},[770],{"type":43,"value":771}," expectedClientState",{"type":38,"tag":140,"props":773,"children":774},{"style":327},[775],{"type":43,"value":341},{"type":38,"tag":140,"props":777,"children":778},{"style":344},[779],{"type":43,"value":780}," cfg.",{"type":38,"tag":140,"props":782,"children":783},{"style":399},[784],{"type":43,"value":785},"get",{"type":38,"tag":140,"props":787,"children":788},{"style":344},[789],{"type":43,"value":407},{"type":38,"tag":140,"props":791,"children":792},{"style":375},[793],{"type":43,"value":794},"\"GRAPH_CLIENT_STATE\"",{"type":38,"tag":140,"props":796,"children":797},{"style":344},[798],{"type":43,"value":416},{"type":38,"tag":140,"props":800,"children":801},{"class":142,"line":180},[802,806,811,815,820,825,830,835,840,845],{"type":38,"tag":140,"props":803,"children":804},{"style":327},[805],{"type":43,"value":330},{"type":38,"tag":140,"props":807,"children":808},{"style":333},[809],{"type":43,"value":810}," notifications",{"type":38,"tag":140,"props":812,"children":813},{"style":327},[814],{"type":43,"value":341},{"type":38,"tag":140,"props":816,"children":817},{"style":344},[818],{"type":43,"value":819}," Array.",{"type":38,"tag":140,"props":821,"children":822},{"style":399},[823],{"type":43,"value":824},"isArray",{"type":38,"tag":140,"props":826,"children":827},{"style":344},[828],{"type":43,"value":829},"(payload.value) ",{"type":38,"tag":140,"props":831,"children":832},{"style":327},[833],{"type":43,"value":834},"?",{"type":38,"tag":140,"props":836,"children":837},{"style":344},[838],{"type":43,"value":839}," payload.value ",{"type":38,"tag":140,"props":841,"children":842},{"style":327},[843],{"type":43,"value":844},":",{"type":38,"tag":140,"props":846,"children":847},{"style":344},[848],{"type":43,"value":849}," []\n",{"type":38,"tag":140,"props":851,"children":852},{"class":142,"line":190},[853],{"type":38,"tag":140,"props":854,"children":855},{"emptyLinePlaceholder":184},[856],{"type":43,"value":187},{"type":38,"tag":140,"props":858,"children":859},{"class":142,"line":419},[860,864,869,873,878,883,888,893,897,901,906,910,915,920,926],{"type":38,"tag":140,"props":861,"children":862},{"style":327},[863],{"type":43,"value":330},{"type":38,"tag":140,"props":865,"children":866},{"style":333},[867],{"type":43,"value":868}," valid",{"type":38,"tag":140,"props":870,"children":871},{"style":327},[872],{"type":43,"value":341},{"type":38,"tag":140,"props":874,"children":875},{"style":344},[876],{"type":43,"value":877}," notifications.",{"type":38,"tag":140,"props":879,"children":880},{"style":333},[881],{"type":43,"value":882},"length",{"type":38,"tag":140,"props":884,"children":885},{"style":327},[886],{"type":43,"value":887}," >",{"type":38,"tag":140,"props":889,"children":890},{"style":333},[891],{"type":43,"value":892}," 0",{"type":38,"tag":140,"props":894,"children":895},{"style":327},[896],{"type":43,"value":383},{"type":38,"tag":140,"props":898,"children":899},{"style":344},[900],{"type":43,"value":877},{"type":38,"tag":140,"props":902,"children":903},{"style":399},[904],{"type":43,"value":905},"every",{"type":38,"tag":140,"props":907,"children":908},{"style":344},[909],{"type":43,"value":407},{"type":38,"tag":140,"props":911,"children":912},{"style":327},[913],{"type":43,"value":914},"function",{"type":38,"tag":140,"props":916,"children":917},{"style":344},[918],{"type":43,"value":919}," (",{"type":38,"tag":140,"props":921,"children":923},{"style":922},"--shiki-default:#FFAB70",[924],{"type":43,"value":925},"item",{"type":38,"tag":140,"props":927,"children":928},{"style":344},[929],{"type":43,"value":930},") {\n",{"type":38,"tag":140,"props":932,"children":933},{"class":142,"line":454},[934,939,944,948],{"type":38,"tag":140,"props":935,"children":936},{"style":327},[937],{"type":43,"value":938},"  return",{"type":38,"tag":140,"props":940,"children":941},{"style":344},[942],{"type":43,"value":943}," item.clientState ",{"type":38,"tag":140,"props":945,"children":946},{"style":327},[947],{"type":43,"value":372},{"type":38,"tag":140,"props":949,"children":950},{"style":344},[951],{"type":43,"value":952}," expectedClientState\n",{"type":38,"tag":140,"props":954,"children":955},{"class":142,"line":472},[956],{"type":38,"tag":140,"props":957,"children":958},{"style":344},[959],{"type":43,"value":960},"})\n",{"type":38,"tag":140,"props":962,"children":963},{"class":142,"line":490},[964],{"type":38,"tag":140,"props":965,"children":966},{"emptyLinePlaceholder":184},[967],{"type":43,"value":187},{"type":38,"tag":140,"props":969,"children":970},{"class":142,"line":499},[971,975,979,984],{"type":38,"tag":140,"props":972,"children":973},{"style":327},[974],{"type":43,"value":362},{"type":38,"tag":140,"props":976,"children":977},{"style":344},[978],{"type":43,"value":919},{"type":38,"tag":140,"props":980,"children":981},{"style":327},[982],{"type":43,"value":983},"!",{"type":38,"tag":140,"props":985,"children":986},{"style":344},[987],{"type":43,"value":988},"valid) {\n",{"type":38,"tag":140,"props":990,"children":992},{"class":142,"line":991},10,[993,997,1001,1005,1010],{"type":38,"tag":140,"props":994,"children":995},{"style":344},[996],{"type":43,"value":396},{"type":38,"tag":140,"props":998,"children":999},{"style":399},[1000],{"type":43,"value":402},{"type":38,"tag":140,"props":1002,"children":1003},{"style":344},[1004],{"type":43,"value":407},{"type":38,"tag":140,"props":1006,"children":1007},{"style":333},[1008],{"type":43,"value":1009},"403",{"type":38,"tag":140,"props":1011,"children":1012},{"style":344},[1013],{"type":43,"value":416},{"type":38,"tag":140,"props":1015,"children":1017},{"class":142,"line":1016},11,[1018,1022,1026,1030,1035],{"type":38,"tag":140,"props":1019,"children":1020},{"style":344},[1021],{"type":43,"value":396},{"type":38,"tag":140,"props":1023,"children":1024},{"style":399},[1025],{"type":43,"value":464},{"type":38,"tag":140,"props":1027,"children":1028},{"style":344},[1029],{"type":43,"value":407},{"type":38,"tag":140,"props":1031,"children":1032},{"style":375},[1033],{"type":43,"value":1034},"\"invalid clientState\"",{"type":38,"tag":140,"props":1036,"children":1037},{"style":344},[1038],{"type":43,"value":416},{"type":38,"tag":140,"props":1040,"children":1042},{"class":142,"line":1041},12,[1043,1047,1051],{"type":38,"tag":140,"props":1044,"children":1045},{"style":344},[1046],{"type":43,"value":396},{"type":38,"tag":140,"props":1048,"children":1049},{"style":399},[1050],{"type":43,"value":482},{"type":38,"tag":140,"props":1052,"children":1053},{"style":344},[1054],{"type":43,"value":487},{"type":38,"tag":140,"props":1056,"children":1058},{"class":142,"line":1057},13,[1059],{"type":38,"tag":140,"props":1060,"children":1061},{"style":327},[1062],{"type":43,"value":496},{"type":38,"tag":140,"props":1064,"children":1066},{"class":142,"line":1065},14,[1067],{"type":38,"tag":140,"props":1068,"children":1069},{"style":344},[1070],{"type":43,"value":505},{"type":38,"tag":39,"props":1072,"children":1073},{},[1074,1076,1082],{"type":43,"value":1075},"Add ",{"type":38,"tag":46,"props":1077,"children":1079},{"className":1078},[],[1080],{"type":43,"value":1081},"GRAPH_CLIENT_STATE",{"type":43,"value":1083}," as a function configuration value. Do not hard-code the real value in shared source.",{"type":38,"tag":39,"props":1085,"children":1086},{},[1087],{"type":43,"value":1088},"The validation check can live in the same input function after the handshake branch. Your application should still authorize resource access independently and follow Microsoft guidance for validating tokens included with rich notifications.",{"type":38,"tag":118,"props":1090,"children":1092},{"id":1091},"_5-respond-quickly-and-process-asynchronously",[1093],{"type":43,"value":1094},"5. Respond quickly and process asynchronously",{"type":38,"tag":39,"props":1096,"children":1097},{},[1098,1100,1106,1108,1114],{"type":43,"value":1099},"Microsoft Graph considers a ",{"type":38,"tag":46,"props":1101,"children":1103},{"className":1102},[],[1104],{"type":43,"value":1105},"2xx",{"type":43,"value":1107}," response within three seconds a timely delivery for ordinary notifications. If processing may take longer, acknowledge the event and put the work on a queue. The Microsoft documentation describes ",{"type":38,"tag":46,"props":1109,"children":1111},{"className":1110},[],[1112],{"type":43,"value":1113},"202 Accepted",{"type":43,"value":1115}," for queued processing and retries for slow or unsuccessful endpoints.",{"type":38,"tag":39,"props":1117,"children":1118},{},[1119],{"type":43,"value":1120},"Avoid doing slow directory, mail or database calls before acknowledging the notification. Webhook Relay can forward quickly, but a synchronous destination still controls total response time.",{"type":38,"tag":118,"props":1122,"children":1124},{"id":1123},"troubleshooting",[1125],{"type":43,"value":1126},"Troubleshooting",{"type":38,"tag":1128,"props":1129,"children":1131},"h3",{"id":1130},"subscription-creation-returns-invalidrequest",[1132,1134],{"type":43,"value":1133},"Subscription creation returns ",{"type":38,"tag":46,"props":1135,"children":1137},{"className":1136},[],[1138],{"type":43,"value":1139},"InvalidRequest",{"type":38,"tag":39,"props":1141,"children":1142},{},[1143,1145,1150],{"type":43,"value":1144},"Inspect the validation request. Confirm the input function is attached, the method is ",{"type":38,"tag":46,"props":1146,"children":1148},{"className":1147},[],[1149],{"type":43,"value":51},{"type":43,"value":1151},", and the body is exactly the decoded token.",{"type":38,"tag":1128,"props":1153,"children":1155},{"id":1154},"the-response-is-json",[1156],{"type":43,"value":1157},"The response is JSON",{"type":38,"tag":39,"props":1159,"children":1160},{},[1161,1167,1169,1175,1177,1183,1185,1190],{"type":38,"tag":46,"props":1162,"children":1164},{"className":1163},[],[1165],{"type":43,"value":1166},"JSON.stringify(validationToken)",{"type":43,"value":1168}," adds quotes and fails validation. Pass the token string directly to ",{"type":38,"tag":46,"props":1170,"children":1172},{"className":1171},[],[1173],{"type":43,"value":1174},"r.setResponseBody",{"type":43,"value":1176}," and set ",{"type":38,"tag":46,"props":1178,"children":1180},{"className":1179},[],[1181],{"type":43,"value":1182},"Content-Type",{"type":43,"value":1184}," to ",{"type":38,"tag":46,"props":1186,"children":1188},{"className":1187},[],[1189],{"type":43,"value":67},{"type":43,"value":262},{"type":38,"tag":1128,"props":1192,"children":1194},{"id":1193},"the-token-contains-spaces-or-punctuation",[1195],{"type":43,"value":1196},"The token contains spaces or punctuation",{"type":38,"tag":39,"props":1198,"children":1199},{},[1200,1202,1207,1209,1215],{"type":43,"value":1201},"Use ",{"type":38,"tag":46,"props":1203,"children":1205},{"className":1204},[],[1206],{"type":43,"value":514},{"type":43,"value":1208},", which gives the parsed query value. Do not copy a percent-encoded value from ",{"type":38,"tag":46,"props":1210,"children":1212},{"className":1211},[],[1213],{"type":43,"value":1214},"r.rawQuery",{"type":43,"value":1216}," into the response.",{"type":38,"tag":1128,"props":1218,"children":1220},{"id":1219},"validation-works-but-notifications-are-rejected",[1221],{"type":43,"value":1222},"Validation works, but notifications are rejected",{"type":38,"tag":39,"props":1224,"children":1225},{},[1226,1228,1233],{"type":43,"value":1227},"Compare the configured ",{"type":38,"tag":46,"props":1229,"children":1231},{"className":1230},[],[1232],{"type":43,"value":1081},{"type":43,"value":1234}," with every item in the notification array. Also confirm the output function was not attached to the input by mistake and that the application accepts batched notifications.",{"type":38,"tag":1128,"props":1236,"children":1238},{"id":1237},"notifications-arrive-more-than-once",[1239],{"type":43,"value":1240},"Notifications arrive more than once",{"type":38,"tag":39,"props":1242,"children":1243},{},[1244,1246,1252],{"type":43,"value":1245},"Design the consumer to be idempotent. Use the subscription ID plus resource data or another stable event identity to avoid applying the same change twice. See ",{"type":38,"tag":203,"props":1247,"children":1249},{"href":1248},"/blog/webhook-retries-and-idempotency",[1250],{"type":43,"value":1251},"webhook retries and idempotency",{"type":43,"value":262},{"type":38,"tag":39,"props":1254,"children":1255},{},[1256,1258,1264,1266,1272,1274,1280],{"type":43,"value":1257},"Microsoft Graph is one example of a provider validation handshake. Meta uses a different ",{"type":38,"tag":46,"props":1259,"children":1261},{"className":1260},[],[1262],{"type":43,"value":1263},"GET",{"type":43,"value":1265}," challenge flow, covered in the ",{"type":38,"tag":203,"props":1267,"children":1269},{"href":1268},"/blog/whatsapp-cloud-api-webhooks",[1270],{"type":43,"value":1271},"WhatsApp Cloud API webhook guide",{"type":43,"value":1273},". For reusable response patterns, see the ",{"type":38,"tag":203,"props":1275,"children":1277},{"href":1276},"/blog/auto-transform-webhook",[1278],{"type":43,"value":1279},"transformation cookbook",{"type":43,"value":262},{"type":38,"tag":1282,"props":1283,"children":1284},"style",{},[1285],{"type":43,"value":1286},"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":180,"depth":180,"links":1288},[1289,1290,1291,1293,1294,1295,1296],{"id":120,"depth":171,"text":123},{"id":236,"depth":171,"text":239},{"id":290,"depth":171,"text":1292},"2. Answer validationToken in a JavaScript function",{"id":524,"depth":171,"text":527},{"id":694,"depth":171,"text":697},{"id":1091,"depth":171,"text":1094},{"id":1123,"depth":171,"text":1126,"children":1297},[1298,1300,1301,1302,1303],{"id":1130,"depth":180,"text":1299},"Subscription creation returns InvalidRequest",{"id":1154,"depth":180,"text":1157},{"id":1193,"depth":180,"text":1196},{"id":1219,"depth":180,"text":1222},{"id":1237,"depth":180,"text":1240},"markdown","content:blog:microsoft-graph-webhook-validation.md","content","blog/microsoft-graph-webhook-validation.md","blog/microsoft-graph-webhook-validation","md",{"loc":4},1788727850846]