[{"data":1,"prerenderedAt":3267},["ShallowReactive",2],{"guide:webhooks":3},{"id":4,"title":5,"body":6,"date":3256,"description":3257,"extension":3258,"meta":3259,"navigation":1270,"path":3260,"seo":3261,"stem":3262,"tags":3263,"updated":3265,"__hash__":3266},"guides\u002Fdocs\u002Fwebhooks.md","Webhooks and Event Delivery",{"type":7,"value":8,"toc":3244},"minimark",[9,18,23,38,132,268,294,305,309,702,729,740,744,751,1012,1043,1047,1129,1135,1139,1142,1150,1165,1208,1211,1225,2937,2945,2949,2964,3019,3032,3045,3050,3061,3065,3071,3075,3082,3127,3219,3225,3240],[10,11,12,13,17],"p",{},"Everything that happens on a linked number — an inbound message, a read receipt, someone joining\na group, the phone being logged out — reaches you as a webhook: an HTTPS ",[14,15,16],"code",{},"POST"," to a URL you\nregister, signed so you can prove it came from Wapito. This page is the contract for those\ndeliveries: the events, the envelope, the headers, the signature and how to verify it, and what\nhappens when your endpoint does not answer.",[19,20,22],"h2",{"id":21},"registering-a-webhook","Registering a webhook",[10,24,25,26,30,31,37],{},"A webhook is a URL, a list of events and a signing secret. Create it from the channel's\n",[27,28,29],"strong",{},"Settings"," tab or with ",[32,33],"endpoint-ref",{"label":34,"op":35,"tag":36},"POST \u002Fwebhooks","create-webhook","webhooks",":",[39,40,45],"pre",{"className":41,"code":42,"language":43,"meta":44,"style":44},"language-bash shiki shiki-themes github-light-default github-dark-default","curl -X POST https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fwebhooks \\\n  -H \"Authorization: Bearer $WAPITO_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"url\": \"https:\u002F\u002Fhooks.example.com\u002Fwapito\",\n    \"events\": [\"messages\", \"messages.status\", \"channel\"],\n    \"headers\": {\"X-Tenant\": \"eu-1\"}\n  }'\n","bash","",[14,46,47,71,89,99,108,114,120,126],{"__ignoreMap":44},[48,49,52,56,60,64,67],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sTDnQ","curl",[48,57,59],{"class":58},"sHrmB"," -X",[48,61,63],{"class":62},"sSVrQ"," POST",[48,65,66],{"class":62}," https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fwebhooks",[48,68,70],{"class":69},"sjeE4"," \\\n",[48,72,74,77,80,84,87],{"class":50,"line":73},2,[48,75,76],{"class":58},"  -H",[48,78,79],{"class":62}," \"Authorization: Bearer ",[48,81,83],{"class":82},"s4rv2","$WAPITO_TOKEN",[48,85,86],{"class":62},"\"",[48,88,70],{"class":69},[48,90,92,94,97],{"class":50,"line":91},3,[48,93,76],{"class":58},[48,95,96],{"class":62}," \"Content-Type: application\u002Fjson\"",[48,98,70],{"class":69},[48,100,102,105],{"class":50,"line":101},4,[48,103,104],{"class":58},"  -d",[48,106,107],{"class":62}," '{\n",[48,109,111],{"class":50,"line":110},5,[48,112,113],{"class":62},"    \"url\": \"https:\u002F\u002Fhooks.example.com\u002Fwapito\",\n",[48,115,117],{"class":50,"line":116},6,[48,118,119],{"class":62},"    \"events\": [\"messages\", \"messages.status\", \"channel\"],\n",[48,121,123],{"class":50,"line":122},7,[48,124,125],{"class":62},"    \"headers\": {\"X-Tenant\": \"eu-1\"}\n",[48,127,129],{"class":50,"line":128},8,[48,130,131],{"class":62},"  }'\n",[39,133,137],{"className":134,"code":135,"language":136,"meta":44,"style":44},"language-json shiki shiki-themes github-light-default github-dark-default","{\n  \"id\": \"whk_01JRQ8F4X9N2K7YB3C5V6W8H0T\",\n  \"url\": \"https:\u002F\u002Fhooks.example.com\u002Fwapito\",\n  \"events\": [\"messages\", \"messages.status\", \"channel\"],\n  \"headers\": {\"X-Tenant\": \"eu-1\"},\n  \"method\": \"POST\",\n  \"enabled\": true,\n  \"secret\": \"whsec_4Kz9…\",\n  \"secret_preview\": \"whsec_…f31a\"\n}\n","json",[14,138,139,144,159,171,196,215,227,239,251,262],{"__ignoreMap":44},[48,140,141],{"class":50,"line":51},[48,142,143],{"class":82},"{\n",[48,145,146,150,153,156],{"class":50,"line":73},[48,147,149],{"class":148},"sjgCt","  \"id\"",[48,151,152],{"class":82},": ",[48,154,155],{"class":62},"\"whk_01JRQ8F4X9N2K7YB3C5V6W8H0T\"",[48,157,158],{"class":82},",\n",[48,160,161,164,166,169],{"class":50,"line":91},[48,162,163],{"class":148},"  \"url\"",[48,165,152],{"class":82},[48,167,168],{"class":62},"\"https:\u002F\u002Fhooks.example.com\u002Fwapito\"",[48,170,158],{"class":82},[48,172,173,176,179,182,185,188,190,193],{"class":50,"line":101},[48,174,175],{"class":148},"  \"events\"",[48,177,178],{"class":82},": [",[48,180,181],{"class":62},"\"messages\"",[48,183,184],{"class":82},", ",[48,186,187],{"class":62},"\"messages.status\"",[48,189,184],{"class":82},[48,191,192],{"class":62},"\"channel\"",[48,194,195],{"class":82},"],\n",[48,197,198,201,204,207,209,212],{"class":50,"line":110},[48,199,200],{"class":148},"  \"headers\"",[48,202,203],{"class":82},": {",[48,205,206],{"class":148},"\"X-Tenant\"",[48,208,152],{"class":82},[48,210,211],{"class":62},"\"eu-1\"",[48,213,214],{"class":82},"},\n",[48,216,217,220,222,225],{"class":50,"line":116},[48,218,219],{"class":148},"  \"method\"",[48,221,152],{"class":82},[48,223,224],{"class":62},"\"POST\"",[48,226,158],{"class":82},[48,228,229,232,234,237],{"class":50,"line":122},[48,230,231],{"class":148},"  \"enabled\"",[48,233,152],{"class":82},[48,235,236],{"class":58},"true",[48,238,158],{"class":82},[48,240,241,244,246,249],{"class":50,"line":128},[48,242,243],{"class":148},"  \"secret\"",[48,245,152],{"class":82},[48,247,248],{"class":62},"\"whsec_4Kz9…\"",[48,250,158],{"class":82},[48,252,254,257,259],{"class":50,"line":253},9,[48,255,256],{"class":148},"  \"secret_preview\"",[48,258,152],{"class":82},[48,260,261],{"class":62},"\"whsec_…f31a\"\n",[48,263,265],{"class":50,"line":264},10,[48,266,267],{"class":82},"}\n",[10,269,270,271,274,275,278,279,281,282,285,286,289,290,293],{},"The ",[14,272,273],{},"secret"," appears in that ",[14,276,277],{},"201"," response and nowhere else afterwards; store it with the\nchannel token. You can supply your own ",[14,280,273],{}," in the request instead of having one generated.\nEvent filters are exact names, prefix wildcards such as ",[14,283,284],{},"messages.*",", or ",[14,287,288],{},"*"," for everything.\n",[14,291,292],{},"enabled: false"," keeps the subscription but skips it, which is how you pause an endpoint during a\ndeploy without losing its configuration. Sandbox channels may register two webhooks and Premium\nchannels five.",[10,295,296,297,300,301,304],{},"The URL must be ",[14,298,299],{},"https:\u002F\u002F"," and must resolve to a public address. A hostname that points at a\nprivate, loopback or link-local range is refused with ",[14,302,303],{},"422 webhook_url_invalid",", and the check is\nrepeated at every send, so a name cannot pass validation and later resolve somewhere internal.",[19,306,308],{"id":307},"events","Events",[310,311,312,328],"table",{},[313,314,315],"thead",{},[316,317,318,322,325],"tr",{},[319,320,321],"th",{},"Event",[319,323,324],{},"When it fires",[319,326,327],{},"Data",[329,330,331,351,378,402,452,478,500,528,556,581,603,630,657,678],"tbody",{},[316,332,333,341,344],{},[334,335,336],"td",{},[48,337,339],{"id":338},"messages",[14,340,338],{},[334,342,343],{},"Every inbound and outbound message, including ones sent from the phone.",[334,345,346,347,350],{},"The message object, with ",[14,348,349],{},"from_me"," set.",[316,352,353,361,364],{},[334,354,355],{},[48,356,358],{"id":357},"messages-edited",[14,359,360],{},"messages.edited",[334,362,363],{},"A message was edited.",[334,365,366,184,369,184,372,184,375],{},[14,367,368],{},"message_id",[14,370,371],{},"chat_id",[14,373,374],{},"body",[14,376,377],{},"timestamp",[316,379,380,388,391],{},[334,381,382],{},[48,383,385],{"id":384},"messages-deleted",[14,386,387],{},"messages.deleted",[334,389,390],{},"A message was deleted for everyone.",[334,392,393,184,395,184,397,184,400],{},[14,394,368],{},[14,396,371],{},[14,398,399],{},"from",[14,401,377],{},[316,403,404,412,435],{},[334,405,406],{},[48,407,409],{"id":408},"messages-status",[14,410,411],{},"messages.status",[334,413,414,415,184,418,184,421,184,424,184,427,430,431,434],{},"A delivery receipt: ",[14,416,417],{},"failed",[14,419,420],{},"pending",[14,422,423],{},"sent",[14,425,426],{},"delivered",[14,428,429],{},"read"," or ",[14,432,433],{},"played",".",[334,436,437,184,439,184,441,184,444,184,447,184,450],{},[14,438,368],{},[14,440,371],{},[14,442,443],{},"recipient",[14,445,446],{},"status",[14,448,449],{},"ack",[14,451,377],{},[316,453,454,462,465],{},[334,455,456],{},[48,457,459],{"id":458},"messages-reactions",[14,460,461],{},"messages.reactions",[334,463,464],{},"A reaction was added or removed.",[334,466,467,184,469,184,471,184,473,184,476],{},[14,468,368],{},[14,470,371],{},[14,472,399],{},[14,474,475],{},"emoji",[14,477,377],{},[316,479,480,487,490],{},[334,481,482],{},[48,483,485],{"id":484},"chats",[14,486,484],{},[334,488,489],{},"A chat was archived or unarchived.",[334,491,492,184,495,184,497],{},[14,493,494],{},"action",[14,496,371],{},[14,498,499],{},"archived",[316,501,502,509,512],{},[334,503,504],{},[48,505,507],{"id":506},"contacts",[14,508,506],{},[334,510,511],{},"A contact was seen for the first time, or its phone, LID or name changed.",[334,513,514,184,516,184,519,184,522,184,525],{},[14,515,494],{},[14,517,518],{},"id",[14,520,521],{},"phone",[14,523,524],{},"lid",[14,526,527],{},"name",[316,529,530,537,540],{},[334,531,532],{},[48,533,535],{"id":534},"groups",[14,536,534],{},[334,538,539],{},"The channel joined or left a group, or a subject, description or picture changed.",[334,541,542,184,544,184,547,184,550,184,553],{},[14,543,494],{},[14,545,546],{},"group_id",[14,548,549],{},"subject",[14,551,552],{},"description",[14,554,555],{},"picture",[316,557,558,566,569],{},[334,559,560],{},[48,561,563],{"id":562},"groups-participants",[14,564,565],{},"groups.participants",[334,567,568],{},"Participants added, removed, promoted or demoted, or a join request.",[334,570,571,184,573,184,575,184,578],{},[14,572,546],{},[14,574,494],{},[14,576,577],{},"participants[]",[14,579,580],{},"by",[316,582,583,590,593],{},[334,584,585],{},[48,586,588],{"id":587},"presences",[14,589,587],{},[334,591,592],{},"Online, offline, typing and recording updates.",[334,594,595,184,597,184,600],{},[14,596,371],{},[14,598,599],{},"presence",[14,601,602],{},"last_seen",[316,604,605,612,615],{},[334,606,607],{},[48,608,610],{"id":609},"polls",[14,611,609],{},[334,613,614],{},"A vote was cast, changed, or could not be decrypted.",[334,616,617,184,620,184,622,184,625,184,628],{},[14,618,619],{},"poll_message_id",[14,621,371],{},[14,623,624],{},"voter",[14,626,627],{},"selected_options[]",[14,629,417],{},[316,631,632,639,642],{},[334,633,634],{},[48,635,637],{"id":636},"calls",[14,638,636],{},[334,640,641],{},"An incoming call was received, accepted or rejected.",[334,643,644,184,647,184,649,184,651,184,654],{},[14,645,646],{},"call_id",[14,648,399],{},[14,650,446],{},[14,652,653],{},"is_video",[14,655,656],{},"is_group",[316,658,659,666,669],{},[334,660,661],{},[48,662,664],{"id":663},"labels",[14,665,663],{},[334,667,668],{},"Business labels created, deleted, or attached to a chat (NOWEB engine).",[334,670,671,184,673,184,676],{},[14,672,494],{},[14,674,675],{},"label",[14,677,371],{},[316,679,680,687,690],{},[334,681,682],{},[48,683,685],{"id":684},"channel",[14,686,684],{},[334,688,689],{},"The channel connected, disconnected, was logged out or banned.",[334,691,692,184,694,184,697,184,699],{},[14,693,446],{},[14,695,696],{},"previous_status",[14,698,521],{},[14,700,701],{},"reason",[10,703,270,704,706,707,709,710,184,713,184,716,430,719,722,723,728],{},[14,705,684],{}," event is the one to subscribe to even if you want nothing else: ",[14,708,701],{}," is\n",[14,711,712],{},"banned",[14,714,715],{},"logged_out",[14,717,718],{},"user_logout",[14,720,721],{},"engine_failed",", and it is how you learn a number is\ngone before a customer tells you. The ",[724,725,727],"a",{"href":726},"\u002Fdocs\u002Fanti-ban-guide\u002F","anti-ban guide"," covers what to do\nwhen it arrives.",[10,730,731,732,735,736,739],{},"Event names are stable. The engine's own event stream is never forwarded — the payloads below\nare Wapito's schema, the same whichever engine the channel runs on — unless you switch on\n",[14,733,734],{},"include_raw"," in the channel settings, which adds a ",[14,737,738],{},"raw"," field to message objects for debugging.",[19,741,743],{"id":742},"the-envelope","The envelope",[10,745,746,747,750],{},"Every delivery has the same shape. ",[14,748,749],{},"data"," varies by event; everything around it does not:",[39,752,754],{"className":134,"code":753,"language":136,"meta":44,"style":44},"{\n  \"id\": \"evt_01JRQ8F4X9N2K7YB3C5V6W8H0T\",\n  \"event\": \"messages\",\n  \"channel_id\": \"ch_01JRQ8F4X9N2K7YB3C5V6W8H0T\",\n  \"timestamp\": 1789459200123,\n  \"api_version\": \"v1\",\n  \"data\": {\n    \"id\": \"false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55\",\n    \"chat_id\": \"15551234567@s.whatsapp.net\",\n    \"from\": \"15551234567\",\n    \"from_lid\": null,\n    \"from_name\": \"Dana\",\n    \"from_me\": false,\n    \"type\": \"text\",\n    \"timestamp\": 1789459200000,\n    \"source\": \"app\",\n    \"text\": { \"body\": \"Hi! Is the blue one still available?\" },\n    \"context\": { \"quoted_id\": null, \"quoted_author\": null, \"forwarded\": false, \"mentions\": [] }\n  }\n}\n",[14,755,756,760,771,782,794,806,818,826,838,850,862,875,888,901,914,927,940,960,1001,1007],{"__ignoreMap":44},[48,757,758],{"class":50,"line":51},[48,759,143],{"class":82},[48,761,762,764,766,769],{"class":50,"line":73},[48,763,149],{"class":148},[48,765,152],{"class":82},[48,767,768],{"class":62},"\"evt_01JRQ8F4X9N2K7YB3C5V6W8H0T\"",[48,770,158],{"class":82},[48,772,773,776,778,780],{"class":50,"line":91},[48,774,775],{"class":148},"  \"event\"",[48,777,152],{"class":82},[48,779,181],{"class":62},[48,781,158],{"class":82},[48,783,784,787,789,792],{"class":50,"line":101},[48,785,786],{"class":148},"  \"channel_id\"",[48,788,152],{"class":82},[48,790,791],{"class":62},"\"ch_01JRQ8F4X9N2K7YB3C5V6W8H0T\"",[48,793,158],{"class":82},[48,795,796,799,801,804],{"class":50,"line":110},[48,797,798],{"class":148},"  \"timestamp\"",[48,800,152],{"class":82},[48,802,803],{"class":58},"1789459200123",[48,805,158],{"class":82},[48,807,808,811,813,816],{"class":50,"line":116},[48,809,810],{"class":148},"  \"api_version\"",[48,812,152],{"class":82},[48,814,815],{"class":62},"\"v1\"",[48,817,158],{"class":82},[48,819,820,823],{"class":50,"line":122},[48,821,822],{"class":148},"  \"data\"",[48,824,825],{"class":82},": {\n",[48,827,828,831,833,836],{"class":50,"line":128},[48,829,830],{"class":148},"    \"id\"",[48,832,152],{"class":82},[48,834,835],{"class":62},"\"false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55\"",[48,837,158],{"class":82},[48,839,840,843,845,848],{"class":50,"line":253},[48,841,842],{"class":148},"    \"chat_id\"",[48,844,152],{"class":82},[48,846,847],{"class":62},"\"15551234567@s.whatsapp.net\"",[48,849,158],{"class":82},[48,851,852,855,857,860],{"class":50,"line":264},[48,853,854],{"class":148},"    \"from\"",[48,856,152],{"class":82},[48,858,859],{"class":62},"\"15551234567\"",[48,861,158],{"class":82},[48,863,865,868,870,873],{"class":50,"line":864},11,[48,866,867],{"class":148},"    \"from_lid\"",[48,869,152],{"class":82},[48,871,872],{"class":58},"null",[48,874,158],{"class":82},[48,876,878,881,883,886],{"class":50,"line":877},12,[48,879,880],{"class":148},"    \"from_name\"",[48,882,152],{"class":82},[48,884,885],{"class":62},"\"Dana\"",[48,887,158],{"class":82},[48,889,891,894,896,899],{"class":50,"line":890},13,[48,892,893],{"class":148},"    \"from_me\"",[48,895,152],{"class":82},[48,897,898],{"class":58},"false",[48,900,158],{"class":82},[48,902,904,907,909,912],{"class":50,"line":903},14,[48,905,906],{"class":148},"    \"type\"",[48,908,152],{"class":82},[48,910,911],{"class":62},"\"text\"",[48,913,158],{"class":82},[48,915,917,920,922,925],{"class":50,"line":916},15,[48,918,919],{"class":148},"    \"timestamp\"",[48,921,152],{"class":82},[48,923,924],{"class":58},"1789459200000",[48,926,158],{"class":82},[48,928,930,933,935,938],{"class":50,"line":929},16,[48,931,932],{"class":148},"    \"source\"",[48,934,152],{"class":82},[48,936,937],{"class":62},"\"app\"",[48,939,158],{"class":82},[48,941,943,946,949,952,954,957],{"class":50,"line":942},17,[48,944,945],{"class":148},"    \"text\"",[48,947,948],{"class":82},": { ",[48,950,951],{"class":148},"\"body\"",[48,953,152],{"class":82},[48,955,956],{"class":62},"\"Hi! Is the blue one still available?\"",[48,958,959],{"class":82}," },\n",[48,961,963,966,968,971,973,975,977,980,982,984,986,989,991,993,995,998],{"class":50,"line":962},18,[48,964,965],{"class":148},"    \"context\"",[48,967,948],{"class":82},[48,969,970],{"class":148},"\"quoted_id\"",[48,972,152],{"class":82},[48,974,872],{"class":58},[48,976,184],{"class":82},[48,978,979],{"class":148},"\"quoted_author\"",[48,981,152],{"class":82},[48,983,872],{"class":58},[48,985,184],{"class":82},[48,987,988],{"class":148},"\"forwarded\"",[48,990,152],{"class":82},[48,992,898],{"class":58},[48,994,184],{"class":82},[48,996,997],{"class":148},"\"mentions\"",[48,999,1000],{"class":82},": [] }\n",[48,1002,1004],{"class":50,"line":1003},19,[48,1005,1006],{"class":82},"  }\n",[48,1008,1010],{"class":50,"line":1009},20,[48,1011,267],{"class":82},[10,1013,1014,1016,1017,1019,1020,1022,1023,1026,1027,1030,1031,1034,1035,1038,1039,434],{},[14,1015,518],{}," is a ULID: unique per event and sortable by time, which makes it the right key for\nde-duplication. ",[14,1018,377],{}," is milliseconds since the epoch. Ids in ",[14,1021,749],{}," always use the\n",[14,1024,1025],{},"@s.whatsapp.net"," form; a group is ",[14,1028,1029],{},"…@g.us",", and a sender whose number WhatsApp hides arrives with\n",[14,1032,1033],{},"from: null"," and a ",[14,1036,1037],{},"from_lid"," you can resolve with\n",[32,1040],{"label":1041,"op":1042,"tag":506},"GET \u002Fcontacts\u002Flid\u002F{lid}","get-contact-by-lid",[19,1044,1046],{"id":1045},"headers","Headers",[310,1048,1049,1059],{},[313,1050,1051],{},[316,1052,1053,1056],{},[319,1054,1055],{},"Header",[319,1057,1058],{},"Value",[329,1060,1061,1073,1086,1096,1106,1116],{},[316,1062,1063,1068],{},[334,1064,1065],{},[14,1066,1067],{},"Content-Type",[334,1069,1070],{},[14,1071,1072],{},"application\u002Fjson",[316,1074,1075,1080],{},[334,1076,1077],{},[14,1078,1079],{},"User-Agent",[334,1081,1082,1085],{},[14,1083,1084],{},"Wapito-Webhooks\u002F1.0"," — allow-list it if your edge filters bots.",[316,1087,1088,1093],{},[334,1089,1090],{},[14,1091,1092],{},"X-Wapito-Event",[334,1094,1095],{},"The event name, so a router can dispatch without parsing the body.",[316,1097,1098,1103],{},[334,1099,1100],{},[14,1101,1102],{},"X-Wapito-Delivery",[334,1104,1105],{},"The delivery job id. Every retry of the same event carries the same value.",[316,1107,1108,1113],{},[334,1109,1110],{},[14,1111,1112],{},"X-Wapito-Channel",[334,1114,1115],{},"The channel id.",[316,1117,1118,1123],{},[334,1119,1120],{},[14,1121,1122],{},"X-Wapito-Signature",[334,1124,1125,1128],{},[14,1126,1127],{},"t=\u003Cms>,v1=\u003Chex>"," — see below.",[10,1130,1131,1132,1134],{},"Your own ",[14,1133,1045],{}," from the subscription are added to every delivery. The six above are\nreserved and cannot be overridden by them.",[19,1136,1138],{"id":1137},"verifying-the-signature","Verifying the signature",[10,1140,1141],{},"The signature header is:",[39,1143,1148],{"className":1144,"code":1146,"language":1147,"meta":44},[1145],"language-text","X-Wapito-Signature: t=1789476800000,v1=35f8a64bb9bcf545b0eb1f28605779fb46c06faf1a1dd90082b964671c56723d\n","text",[14,1149,1146],{"__ignoreMap":44},[10,1151,1152,1153,1156,1157,1160,1161,1164],{},"where ",[14,1154,1155],{},"t"," is the time the delivery was signed, in milliseconds, and ",[14,1158,1159],{},"v1"," is the hex HMAC-SHA256\nof the string ",[14,1162,1163],{},"\"\u003Ct>.\u003Craw body>\""," under your webhook secret. To verify:",[1166,1167,1168,1178,1191,1202],"ol",{},[1169,1170,1171,1172,1174,1175,1177],"li",{},"Parse ",[14,1173,1155],{}," and ",[14,1176,1159],{}," out of the header.",[1169,1179,1180,1181,1184,1185,1188,1189,434],{},"Reject the delivery if ",[14,1182,1183],{},"|now − t|"," is more than ",[27,1186,1187],{},"300 seconds",". The timestamp is inside the\nsigned string, so an old body cannot be replayed with a fresh ",[14,1190,1155],{},[1169,1192,1193,1194,1197,1198,1201],{},"Compute ",[14,1195,1196],{},"HMAC-SHA256(secret, t + \".\" + rawBody)"," over the ",[27,1199,1200],{},"exact bytes"," you received —\nbefore any JSON parsing, re-serialising or whitespace changes.",[1169,1203,1204,1205,1207],{},"Compare it to ",[14,1206,1159],{}," with a constant-time comparison.",[10,1209,1210],{},"Wapito serialises the body once and signs those bytes, so a receiver that hashes the raw request\nalways agrees with us. The one way this reliably goes wrong is a framework that parses JSON before\nyour handler runs and hands you a re-encoded copy; every verifier below reads the raw body first.",[10,1212,1213,1214,1217,1218,158,1221,1224],{},"A test vector you can run without a live channel — secret ",[14,1215,1216],{},"whsec_test",", body ",[14,1219,1220],{},"{\"ok\":true}",[14,1222,1223],{},"t=1789476800000"," — must produce exactly the header shown above.",[1226,1227,1228,1814,2310],"code-group",{},[39,1229,1234],{"className":1230,"code":1231,"filename":1232,"language":1233,"meta":44,"style":44},"language-javascript shiki shiki-themes github-light-default github-dark-default","import crypto from 'node:crypto';\nimport express from 'express';\n\nconst app = express();\nconst SECRET = process.env.WAPITO_WEBHOOK_SECRET;\n\n\u002F** Checks the X-Wapito-Signature header: t=\u003Cms>,v1=\u003Chex hmac-sha256>. *\u002F\nfunction verify(raw, header) {\n  if (!header) return false;\n  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));\n  if (!parts.t || !parts.v1) return false;\n  if (Math.abs(Date.now() - Number(parts.t)) > 300_000) return false; \u002F\u002F 5 minute clock skew\n  const expected = crypto.createHmac('sha256', SECRET).update(`${parts.t}.`).update(raw).digest('hex');\n  const a = Buffer.from(expected, 'hex');\n  const b = Buffer.from(parts.v1, 'hex');\n  return a.length === b.length && crypto.timingSafeEqual(a, b);\n}\n\napp.post('\u002Fwapito', express.raw({ type: 'application\u002Fjson' }), (req, res) => {\n  if (!verify(req.body, req.get('X-Wapito-Signature'))) return res.sendStatus(401);\n  const payload = JSON.parse(req.body.toString('utf8'));\n  console.log(payload.event, payload.data);\n  res.sendStatus(200); \u002F\u002F answer 2xx fast; do the real work in a queue\n});\n\napp.listen(Number(process.env.PORT ?? 3000));\n","Node","javascript",[14,1235,1236,1252,1266,1272,1290,1307,1311,1317,1338,1360,1417,1443,1489,1552,1573,1593,1623,1627,1631,1673,1713,1745,1757,1776,1782,1787],{"__ignoreMap":44},[48,1237,1238,1241,1244,1246,1249],{"class":50,"line":51},[48,1239,1240],{"class":69},"import",[48,1242,1243],{"class":82}," crypto ",[48,1245,399],{"class":69},[48,1247,1248],{"class":62}," 'node:crypto'",[48,1250,1251],{"class":82},";\n",[48,1253,1254,1256,1259,1261,1264],{"class":50,"line":73},[48,1255,1240],{"class":69},[48,1257,1258],{"class":82}," express ",[48,1260,399],{"class":69},[48,1262,1263],{"class":62}," 'express'",[48,1265,1251],{"class":82},[48,1267,1268],{"class":50,"line":91},[48,1269,1271],{"emptyLinePlaceholder":1270},true,"\n",[48,1273,1274,1277,1280,1283,1287],{"class":50,"line":101},[48,1275,1276],{"class":69},"const",[48,1278,1279],{"class":58}," app",[48,1281,1282],{"class":69}," =",[48,1284,1286],{"class":1285},"sbjLL"," express",[48,1288,1289],{"class":82},"();\n",[48,1291,1292,1294,1297,1299,1302,1305],{"class":50,"line":110},[48,1293,1276],{"class":69},[48,1295,1296],{"class":58}," SECRET",[48,1298,1282],{"class":69},[48,1300,1301],{"class":82}," process.env.",[48,1303,1304],{"class":58},"WAPITO_WEBHOOK_SECRET",[48,1306,1251],{"class":82},[48,1308,1309],{"class":50,"line":116},[48,1310,1271],{"emptyLinePlaceholder":1270},[48,1312,1313],{"class":50,"line":122},[48,1314,1316],{"class":1315},"sU953","\u002F** Checks the X-Wapito-Signature header: t=\u003Cms>,v1=\u003Chex hmac-sha256>. *\u002F\n",[48,1318,1319,1322,1325,1328,1330,1332,1335],{"class":50,"line":128},[48,1320,1321],{"class":69},"function",[48,1323,1324],{"class":1285}," verify",[48,1326,1327],{"class":82},"(",[48,1329,738],{"class":54},[48,1331,184],{"class":82},[48,1333,1334],{"class":54},"header",[48,1336,1337],{"class":82},") {\n",[48,1339,1340,1343,1346,1349,1352,1355,1358],{"class":50,"line":253},[48,1341,1342],{"class":69},"  if",[48,1344,1345],{"class":82}," (",[48,1347,1348],{"class":69},"!",[48,1350,1351],{"class":82},"header) ",[48,1353,1354],{"class":69},"return",[48,1356,1357],{"class":58}," false",[48,1359,1251],{"class":82},[48,1361,1362,1365,1368,1370,1373,1376,1379,1382,1384,1387,1390,1393,1396,1398,1401,1404,1407,1409,1411,1414],{"class":50,"line":264},[48,1363,1364],{"class":69},"  const",[48,1366,1367],{"class":58}," parts",[48,1369,1282],{"class":69},[48,1371,1372],{"class":82}," Object.",[48,1374,1375],{"class":1285},"fromEntries",[48,1377,1378],{"class":82},"(header.",[48,1380,1381],{"class":1285},"split",[48,1383,1327],{"class":82},[48,1385,1386],{"class":62},"','",[48,1388,1389],{"class":82},").",[48,1391,1392],{"class":1285},"map",[48,1394,1395],{"class":82},"((",[48,1397,10],{"class":54},[48,1399,1400],{"class":82},") ",[48,1402,1403],{"class":69},"=>",[48,1405,1406],{"class":82}," p.",[48,1408,1381],{"class":1285},[48,1410,1327],{"class":82},[48,1412,1413],{"class":62},"'='",[48,1415,1416],{"class":82},")));\n",[48,1418,1419,1421,1423,1425,1428,1431,1434,1437,1439,1441],{"class":50,"line":864},[48,1420,1342],{"class":69},[48,1422,1345],{"class":82},[48,1424,1348],{"class":69},[48,1426,1427],{"class":82},"parts.t ",[48,1429,1430],{"class":69},"||",[48,1432,1433],{"class":69}," !",[48,1435,1436],{"class":82},"parts.v1) ",[48,1438,1354],{"class":69},[48,1440,1357],{"class":58},[48,1442,1251],{"class":82},[48,1444,1445,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1479,1481,1483,1486],{"class":50,"line":877},[48,1446,1342],{"class":69},[48,1448,1449],{"class":82}," (Math.",[48,1451,1452],{"class":1285},"abs",[48,1454,1455],{"class":82},"(Date.",[48,1457,1458],{"class":1285},"now",[48,1460,1461],{"class":82},"() ",[48,1463,1464],{"class":69},"-",[48,1466,1467],{"class":1285}," Number",[48,1469,1470],{"class":82},"(parts.t)) ",[48,1472,1473],{"class":69},">",[48,1475,1476],{"class":58}," 300_000",[48,1478,1400],{"class":82},[48,1480,1354],{"class":69},[48,1482,1357],{"class":58},[48,1484,1485],{"class":82},"; ",[48,1487,1488],{"class":1315},"\u002F\u002F 5 minute clock skew\n",[48,1490,1491,1493,1496,1498,1501,1504,1506,1509,1511,1514,1516,1519,1521,1524,1527,1529,1531,1534,1536,1538,1541,1544,1546,1549],{"class":50,"line":890},[48,1492,1364],{"class":69},[48,1494,1495],{"class":58}," expected",[48,1497,1282],{"class":69},[48,1499,1500],{"class":82}," crypto.",[48,1502,1503],{"class":1285},"createHmac",[48,1505,1327],{"class":82},[48,1507,1508],{"class":62},"'sha256'",[48,1510,184],{"class":82},[48,1512,1513],{"class":58},"SECRET",[48,1515,1389],{"class":82},[48,1517,1518],{"class":1285},"update",[48,1520,1327],{"class":82},[48,1522,1523],{"class":62},"`${",[48,1525,1526],{"class":82},"parts",[48,1528,434],{"class":62},[48,1530,1155],{"class":82},[48,1532,1533],{"class":62},"}.`",[48,1535,1389],{"class":82},[48,1537,1518],{"class":1285},[48,1539,1540],{"class":82},"(raw).",[48,1542,1543],{"class":1285},"digest",[48,1545,1327],{"class":82},[48,1547,1548],{"class":62},"'hex'",[48,1550,1551],{"class":82},");\n",[48,1553,1554,1556,1559,1561,1564,1566,1569,1571],{"class":50,"line":903},[48,1555,1364],{"class":69},[48,1557,1558],{"class":58}," a",[48,1560,1282],{"class":69},[48,1562,1563],{"class":82}," Buffer.",[48,1565,399],{"class":1285},[48,1567,1568],{"class":82},"(expected, ",[48,1570,1548],{"class":62},[48,1572,1551],{"class":82},[48,1574,1575,1577,1580,1582,1584,1586,1589,1591],{"class":50,"line":916},[48,1576,1364],{"class":69},[48,1578,1579],{"class":58}," b",[48,1581,1282],{"class":69},[48,1583,1563],{"class":82},[48,1585,399],{"class":1285},[48,1587,1588],{"class":82},"(parts.v1, ",[48,1590,1548],{"class":62},[48,1592,1551],{"class":82},[48,1594,1595,1598,1601,1604,1607,1610,1612,1615,1617,1620],{"class":50,"line":929},[48,1596,1597],{"class":69},"  return",[48,1599,1600],{"class":82}," a.",[48,1602,1603],{"class":58},"length",[48,1605,1606],{"class":69}," ===",[48,1608,1609],{"class":82}," b.",[48,1611,1603],{"class":58},[48,1613,1614],{"class":69}," &&",[48,1616,1500],{"class":82},[48,1618,1619],{"class":1285},"timingSafeEqual",[48,1621,1622],{"class":82},"(a, b);\n",[48,1624,1625],{"class":50,"line":942},[48,1626,267],{"class":82},[48,1628,1629],{"class":50,"line":962},[48,1630,1271],{"emptyLinePlaceholder":1270},[48,1632,1633,1636,1639,1641,1644,1647,1649,1652,1655,1658,1661,1663,1666,1668,1670],{"class":50,"line":1003},[48,1634,1635],{"class":82},"app.",[48,1637,1638],{"class":1285},"post",[48,1640,1327],{"class":82},[48,1642,1643],{"class":62},"'\u002Fwapito'",[48,1645,1646],{"class":82},", express.",[48,1648,738],{"class":1285},[48,1650,1651],{"class":82},"({ type: ",[48,1653,1654],{"class":62},"'application\u002Fjson'",[48,1656,1657],{"class":82}," }), (",[48,1659,1660],{"class":54},"req",[48,1662,184],{"class":82},[48,1664,1665],{"class":54},"res",[48,1667,1400],{"class":82},[48,1669,1403],{"class":69},[48,1671,1672],{"class":82}," {\n",[48,1674,1675,1677,1679,1681,1684,1687,1690,1692,1695,1698,1700,1703,1706,1708,1711],{"class":50,"line":1009},[48,1676,1342],{"class":69},[48,1678,1345],{"class":82},[48,1680,1348],{"class":69},[48,1682,1683],{"class":1285},"verify",[48,1685,1686],{"class":82},"(req.body, req.",[48,1688,1689],{"class":1285},"get",[48,1691,1327],{"class":82},[48,1693,1694],{"class":62},"'X-Wapito-Signature'",[48,1696,1697],{"class":82},"))) ",[48,1699,1354],{"class":69},[48,1701,1702],{"class":82}," res.",[48,1704,1705],{"class":1285},"sendStatus",[48,1707,1327],{"class":82},[48,1709,1710],{"class":58},"401",[48,1712,1551],{"class":82},[48,1714,1716,1718,1721,1723,1726,1728,1731,1734,1737,1739,1742],{"class":50,"line":1715},21,[48,1717,1364],{"class":69},[48,1719,1720],{"class":58}," payload",[48,1722,1282],{"class":69},[48,1724,1725],{"class":58}," JSON",[48,1727,434],{"class":82},[48,1729,1730],{"class":1285},"parse",[48,1732,1733],{"class":82},"(req.body.",[48,1735,1736],{"class":1285},"toString",[48,1738,1327],{"class":82},[48,1740,1741],{"class":62},"'utf8'",[48,1743,1744],{"class":82},"));\n",[48,1746,1748,1751,1754],{"class":50,"line":1747},22,[48,1749,1750],{"class":82},"  console.",[48,1752,1753],{"class":1285},"log",[48,1755,1756],{"class":82},"(payload.event, payload.data);\n",[48,1758,1760,1763,1765,1767,1770,1773],{"class":50,"line":1759},23,[48,1761,1762],{"class":82},"  res.",[48,1764,1705],{"class":1285},[48,1766,1327],{"class":82},[48,1768,1769],{"class":58},"200",[48,1771,1772],{"class":82},"); ",[48,1774,1775],{"class":1315},"\u002F\u002F answer 2xx fast; do the real work in a queue\n",[48,1777,1779],{"class":50,"line":1778},24,[48,1780,1781],{"class":82},"});\n",[48,1783,1785],{"class":50,"line":1784},25,[48,1786,1271],{"emptyLinePlaceholder":1270},[48,1788,1790,1792,1795,1797,1800,1803,1806,1809,1812],{"class":50,"line":1789},26,[48,1791,1635],{"class":82},[48,1793,1794],{"class":1285},"listen",[48,1796,1327],{"class":82},[48,1798,1799],{"class":1285},"Number",[48,1801,1802],{"class":82},"(process.env.",[48,1804,1805],{"class":58},"PORT",[48,1807,1808],{"class":69}," ??",[48,1810,1811],{"class":58}," 3000",[48,1813,1744],{"class":82},[39,1815,1820],{"className":1816,"code":1817,"filename":1818,"language":1819,"meta":44,"style":44},"language-python shiki shiki-themes github-light-default github-dark-default","import hashlib\nimport hmac\nimport os\nimport time\n\nfrom fastapi import FastAPI, Header, HTTPException, Request\n\napp = FastAPI()\nSECRET = os.environ[\"WAPITO_WEBHOOK_SECRET\"].encode()\n\n\ndef verify(raw: bytes, header: str | None) -> bool:\n    \"\"\"Checks the X-Wapito-Signature header: t=\u003Cms>,v1=\u003Chex hmac-sha256>.\"\"\"\n    if not header:\n        return False\n    parts = dict(p.split(\"=\", 1) for p in header.split(\",\") if \"=\" in p)\n    ts, sig = parts.get(\"t\"), parts.get(\"v1\")\n    if not ts or not sig:\n        return False\n    if abs(time.time() * 1000 - int(ts)) > 300_000:  # 5 minute clock skew\n        return False\n    expected = hmac.new(SECRET, ts.encode() + b\".\" + raw, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, sig)\n\n\n@app.post(\"\u002Fwapito\")\nasync def wapito(request: Request, signature: str | None = Header(default=None, alias=\"X-Wapito-Signature\")):\n    raw = await request.body()\n    if not verify(raw, signature):\n        raise HTTPException(status_code=401, detail=\"bad signature\")\n    payload = await request.json()\n    print(payload[\"event\"], payload[\"data\"])\n    return {\"ok\": True}  # answer 2xx fast; do the real work in a queue\n","Python","python",[14,1821,1822,1829,1836,1843,1850,1854,1866,1870,1881,1896,1900,1904,1938,1943,1954,1962,2014,2035,2052,2058,2092,2098,2127,2135,2139,2143,2155,2202,2216,2226,2254,2267,2288],{"__ignoreMap":44},[48,1823,1824,1826],{"class":50,"line":51},[48,1825,1240],{"class":69},[48,1827,1828],{"class":82}," hashlib\n",[48,1830,1831,1833],{"class":50,"line":73},[48,1832,1240],{"class":69},[48,1834,1835],{"class":82}," hmac\n",[48,1837,1838,1840],{"class":50,"line":91},[48,1839,1240],{"class":69},[48,1841,1842],{"class":82}," os\n",[48,1844,1845,1847],{"class":50,"line":101},[48,1846,1240],{"class":69},[48,1848,1849],{"class":82}," time\n",[48,1851,1852],{"class":50,"line":110},[48,1853,1271],{"emptyLinePlaceholder":1270},[48,1855,1856,1858,1861,1863],{"class":50,"line":116},[48,1857,399],{"class":69},[48,1859,1860],{"class":82}," fastapi ",[48,1862,1240],{"class":69},[48,1864,1865],{"class":82}," FastAPI, Header, HTTPException, Request\n",[48,1867,1868],{"class":50,"line":122},[48,1869,1271],{"emptyLinePlaceholder":1270},[48,1871,1872,1875,1878],{"class":50,"line":128},[48,1873,1874],{"class":82},"app ",[48,1876,1877],{"class":69},"=",[48,1879,1880],{"class":82}," FastAPI()\n",[48,1882,1883,1885,1887,1890,1893],{"class":50,"line":253},[48,1884,1513],{"class":58},[48,1886,1282],{"class":69},[48,1888,1889],{"class":82}," os.environ[",[48,1891,1892],{"class":62},"\"WAPITO_WEBHOOK_SECRET\"",[48,1894,1895],{"class":82},"].encode()\n",[48,1897,1898],{"class":50,"line":264},[48,1899,1271],{"emptyLinePlaceholder":1270},[48,1901,1902],{"class":50,"line":864},[48,1903,1271],{"emptyLinePlaceholder":1270},[48,1905,1906,1909,1911,1914,1917,1920,1923,1926,1929,1932,1935],{"class":50,"line":877},[48,1907,1908],{"class":69},"def",[48,1910,1324],{"class":1285},[48,1912,1913],{"class":82},"(raw: ",[48,1915,1916],{"class":58},"bytes",[48,1918,1919],{"class":82},", header: ",[48,1921,1922],{"class":58},"str",[48,1924,1925],{"class":69}," |",[48,1927,1928],{"class":58}," None",[48,1930,1931],{"class":82},") -> ",[48,1933,1934],{"class":58},"bool",[48,1936,1937],{"class":82},":\n",[48,1939,1940],{"class":50,"line":890},[48,1941,1942],{"class":62},"    \"\"\"Checks the X-Wapito-Signature header: t=\u003Cms>,v1=\u003Chex hmac-sha256>.\"\"\"\n",[48,1944,1945,1948,1951],{"class":50,"line":903},[48,1946,1947],{"class":69},"    if",[48,1949,1950],{"class":69}," not",[48,1952,1953],{"class":82}," header:\n",[48,1955,1956,1959],{"class":50,"line":916},[48,1957,1958],{"class":69},"        return",[48,1960,1961],{"class":58}," False\n",[48,1963,1964,1967,1969,1972,1975,1978,1980,1983,1985,1988,1991,1994,1997,2000,2002,2005,2008,2011],{"class":50,"line":929},[48,1965,1966],{"class":82},"    parts ",[48,1968,1877],{"class":69},[48,1970,1971],{"class":58}," dict",[48,1973,1974],{"class":82},"(p.split(",[48,1976,1977],{"class":62},"\"=\"",[48,1979,184],{"class":82},[48,1981,1982],{"class":58},"1",[48,1984,1400],{"class":82},[48,1986,1987],{"class":69},"for",[48,1989,1990],{"class":82}," p ",[48,1992,1993],{"class":69},"in",[48,1995,1996],{"class":82}," header.split(",[48,1998,1999],{"class":62},"\",\"",[48,2001,1400],{"class":82},[48,2003,2004],{"class":69},"if",[48,2006,2007],{"class":62}," \"=\"",[48,2009,2010],{"class":69}," in",[48,2012,2013],{"class":82}," p)\n",[48,2015,2016,2019,2021,2024,2027,2030,2032],{"class":50,"line":942},[48,2017,2018],{"class":82},"    ts, sig ",[48,2020,1877],{"class":69},[48,2022,2023],{"class":82}," parts.get(",[48,2025,2026],{"class":62},"\"t\"",[48,2028,2029],{"class":82},"), parts.get(",[48,2031,815],{"class":62},[48,2033,2034],{"class":82},")\n",[48,2036,2037,2039,2041,2044,2047,2049],{"class":50,"line":962},[48,2038,1947],{"class":69},[48,2040,1950],{"class":69},[48,2042,2043],{"class":82}," ts ",[48,2045,2046],{"class":69},"or",[48,2048,1950],{"class":69},[48,2050,2051],{"class":82}," sig:\n",[48,2053,2054,2056],{"class":50,"line":1003},[48,2055,1958],{"class":69},[48,2057,1961],{"class":58},[48,2059,2060,2062,2065,2068,2070,2073,2076,2079,2082,2084,2086,2089],{"class":50,"line":1009},[48,2061,1947],{"class":69},[48,2063,2064],{"class":58}," abs",[48,2066,2067],{"class":82},"(time.time() ",[48,2069,288],{"class":69},[48,2071,2072],{"class":58}," 1000",[48,2074,2075],{"class":69}," -",[48,2077,2078],{"class":58}," int",[48,2080,2081],{"class":82},"(ts)) ",[48,2083,1473],{"class":69},[48,2085,1476],{"class":58},[48,2087,2088],{"class":82},":  ",[48,2090,2091],{"class":1315},"# 5 minute clock skew\n",[48,2093,2094,2096],{"class":50,"line":1715},[48,2095,1958],{"class":69},[48,2097,1961],{"class":58},[48,2099,2100,2103,2105,2108,2110,2113,2116,2118,2121,2124],{"class":50,"line":1747},[48,2101,2102],{"class":82},"    expected ",[48,2104,1877],{"class":69},[48,2106,2107],{"class":82}," hmac.new(",[48,2109,1513],{"class":58},[48,2111,2112],{"class":82},", ts.encode() ",[48,2114,2115],{"class":69},"+",[48,2117,1579],{"class":69},[48,2119,2120],{"class":62},"\".\"",[48,2122,2123],{"class":69}," +",[48,2125,2126],{"class":82}," raw, hashlib.sha256).hexdigest()\n",[48,2128,2129,2132],{"class":50,"line":1759},[48,2130,2131],{"class":69},"    return",[48,2133,2134],{"class":82}," hmac.compare_digest(expected, sig)\n",[48,2136,2137],{"class":50,"line":1778},[48,2138,1271],{"emptyLinePlaceholder":1270},[48,2140,2141],{"class":50,"line":1784},[48,2142,1271],{"emptyLinePlaceholder":1270},[48,2144,2145,2148,2150,2153],{"class":50,"line":1789},[48,2146,2147],{"class":1285},"@app.post",[48,2149,1327],{"class":82},[48,2151,2152],{"class":62},"\"\u002Fwapito\"",[48,2154,2034],{"class":82},[48,2156,2158,2161,2164,2167,2170,2172,2174,2176,2178,2181,2184,2186,2189,2191,2194,2196,2199],{"class":50,"line":2157},27,[48,2159,2160],{"class":69},"async",[48,2162,2163],{"class":69}," def",[48,2165,2166],{"class":1285}," wapito",[48,2168,2169],{"class":82},"(request: Request, signature: ",[48,2171,1922],{"class":58},[48,2173,1925],{"class":69},[48,2175,1928],{"class":58},[48,2177,1282],{"class":69},[48,2179,2180],{"class":82}," Header(",[48,2182,2183],{"class":54},"default",[48,2185,1877],{"class":69},[48,2187,2188],{"class":58},"None",[48,2190,184],{"class":82},[48,2192,2193],{"class":54},"alias",[48,2195,1877],{"class":69},[48,2197,2198],{"class":62},"\"X-Wapito-Signature\"",[48,2200,2201],{"class":82},")):\n",[48,2203,2205,2208,2210,2213],{"class":50,"line":2204},28,[48,2206,2207],{"class":82},"    raw ",[48,2209,1877],{"class":69},[48,2211,2212],{"class":69}," await",[48,2214,2215],{"class":82}," request.body()\n",[48,2217,2219,2221,2223],{"class":50,"line":2218},29,[48,2220,1947],{"class":69},[48,2222,1950],{"class":69},[48,2224,2225],{"class":82}," verify(raw, signature):\n",[48,2227,2229,2232,2235,2238,2240,2242,2244,2247,2249,2252],{"class":50,"line":2228},30,[48,2230,2231],{"class":69},"        raise",[48,2233,2234],{"class":82}," HTTPException(",[48,2236,2237],{"class":54},"status_code",[48,2239,1877],{"class":69},[48,2241,1710],{"class":58},[48,2243,184],{"class":82},[48,2245,2246],{"class":54},"detail",[48,2248,1877],{"class":69},[48,2250,2251],{"class":62},"\"bad signature\"",[48,2253,2034],{"class":82},[48,2255,2257,2260,2262,2264],{"class":50,"line":2256},31,[48,2258,2259],{"class":82},"    payload ",[48,2261,1877],{"class":69},[48,2263,2212],{"class":69},[48,2265,2266],{"class":82}," request.json()\n",[48,2268,2270,2273,2276,2279,2282,2285],{"class":50,"line":2269},32,[48,2271,2272],{"class":58},"    print",[48,2274,2275],{"class":82},"(payload[",[48,2277,2278],{"class":62},"\"event\"",[48,2280,2281],{"class":82},"], payload[",[48,2283,2284],{"class":62},"\"data\"",[48,2286,2287],{"class":82},"])\n",[48,2289,2291,2293,2296,2299,2301,2304,2307],{"class":50,"line":2290},33,[48,2292,2131],{"class":69},[48,2294,2295],{"class":82}," {",[48,2297,2298],{"class":62},"\"ok\"",[48,2300,152],{"class":82},[48,2302,2303],{"class":58},"True",[48,2305,2306],{"class":82},"}  ",[48,2308,2309],{"class":1315},"# answer 2xx fast; do the real work in a queue\n",[39,2311,2316],{"className":2312,"code":2313,"filename":2314,"language":2315,"meta":44,"style":44},"language-php shiki shiki-themes github-light-default github-dark-default","\u003C?php\n\u002F\u002F public\u002Fwebhook.php - run with: php -S 0.0.0.0:8000 -t public\nrequire __DIR__ . '\u002F..\u002Fvendor\u002Fautoload.php';\n\nuse Psr\\Http\\Message\\ResponseInterface as Response;\nuse Psr\\Http\\Message\\ServerRequestInterface as Request;\nuse Slim\\Factory\\AppFactory;\n\n$secret = getenv('WAPITO_WEBHOOK_SECRET');\n\n\u002F** Checks the X-Wapito-Signature header: t=\u003Cms>,v1=\u003Chex hmac-sha256>. *\u002F\nfunction wapito_verify(string $raw, ?string $header, string $secret): bool\n{\n    if (!$header) {\n        return false;\n    }\n    $parts = [];\n    foreach (explode(',', $header) as $pair) {\n        [$k, $v] = array_pad(explode('=', $pair, 2), 2, null);\n        $parts[$k] = $v;\n    }\n    if (empty($parts['t']) || empty($parts['v1'])) {\n        return false;\n    }\n    if (abs((int) (microtime(true) * 1000) - (int) $parts['t']) > 300000) {\n        return false; \u002F\u002F 5 minute clock skew\n    }\n    $expected = hash_hmac('sha256', $parts['t'] . '.' . $raw, $secret);\n    return hash_equals($expected, $parts['v1']);\n}\n\n$app = AppFactory::create();\n$app->post('\u002Fwapito', function (Request $request, Response $response) use ($secret) {\n    $raw = (string) $request->getBody();\n    if (!wapito_verify($raw, $request->getHeaderLine('X-Wapito-Signature'), $secret)) {\n        return $response->withStatus(401);\n    }\n    $payload = json_decode($raw, true);\n    error_log($payload['event'] . ' ' . json_encode($payload['data']));\n    return $response->withStatus(200); \u002F\u002F answer 2xx fast; do the real work in a queue\n});\n$app->run();\n","PHP","php",[14,2317,2318,2326,2331,2347,2351,2367,2381,2390,2394,2411,2415,2419,2450,2454,2465,2473,2478,2488,2511,2546,2556,2560,2591,2599,2603,2654,2664,2668,2700,2715,2719,2723,2741,2778,2800,2827,2846,2851,2869,2901,2920,2925],{"__ignoreMap":44},[48,2319,2320,2323],{"class":50,"line":51},[48,2321,2322],{"class":69},"\u003C?",[48,2324,2325],{"class":58},"php\n",[48,2327,2328],{"class":50,"line":73},[48,2329,2330],{"class":1315},"\u002F\u002F public\u002Fwebhook.php - run with: php -S 0.0.0.0:8000 -t public\n",[48,2332,2333,2336,2339,2342,2345],{"class":50,"line":91},[48,2334,2335],{"class":69},"require",[48,2337,2338],{"class":58}," __DIR__",[48,2340,2341],{"class":69}," .",[48,2343,2344],{"class":62}," '\u002F..\u002Fvendor\u002Fautoload.php'",[48,2346,1251],{"class":82},[48,2348,2349],{"class":50,"line":101},[48,2350,1271],{"emptyLinePlaceholder":1270},[48,2352,2353,2356,2359,2362,2365],{"class":50,"line":110},[48,2354,2355],{"class":69},"use",[48,2357,2358],{"class":58}," Psr\\Http\\Message\\ResponseInterface",[48,2360,2361],{"class":69}," as",[48,2363,2364],{"class":58}," Response",[48,2366,1251],{"class":82},[48,2368,2369,2371,2374,2376,2379],{"class":50,"line":116},[48,2370,2355],{"class":69},[48,2372,2373],{"class":58}," Psr\\Http\\Message\\ServerRequestInterface",[48,2375,2361],{"class":69},[48,2377,2378],{"class":58}," Request",[48,2380,1251],{"class":82},[48,2382,2383,2385,2388],{"class":50,"line":122},[48,2384,2355],{"class":69},[48,2386,2387],{"class":58}," Slim\\Factory\\AppFactory",[48,2389,1251],{"class":82},[48,2391,2392],{"class":50,"line":128},[48,2393,1271],{"emptyLinePlaceholder":1270},[48,2395,2396,2399,2401,2404,2406,2409],{"class":50,"line":253},[48,2397,2398],{"class":82},"$secret ",[48,2400,1877],{"class":69},[48,2402,2403],{"class":58}," getenv",[48,2405,1327],{"class":82},[48,2407,2408],{"class":62},"'WAPITO_WEBHOOK_SECRET'",[48,2410,1551],{"class":82},[48,2412,2413],{"class":50,"line":264},[48,2414,1271],{"emptyLinePlaceholder":1270},[48,2416,2417],{"class":50,"line":864},[48,2418,1316],{"class":1315},[48,2420,2421,2423,2426,2428,2431,2434,2437,2440,2442,2445,2447],{"class":50,"line":877},[48,2422,1321],{"class":69},[48,2424,2425],{"class":1285}," wapito_verify",[48,2427,1327],{"class":82},[48,2429,2430],{"class":69},"string",[48,2432,2433],{"class":82}," $raw, ",[48,2435,2436],{"class":69},"?string",[48,2438,2439],{"class":82}," $header, ",[48,2441,2430],{"class":69},[48,2443,2444],{"class":82}," $secret)",[48,2446,37],{"class":69},[48,2448,2449],{"class":69}," bool\n",[48,2451,2452],{"class":50,"line":890},[48,2453,143],{"class":82},[48,2455,2456,2458,2460,2462],{"class":50,"line":903},[48,2457,1947],{"class":69},[48,2459,1345],{"class":82},[48,2461,1348],{"class":69},[48,2463,2464],{"class":82},"$header) {\n",[48,2466,2467,2469,2471],{"class":50,"line":916},[48,2468,1958],{"class":69},[48,2470,1357],{"class":58},[48,2472,1251],{"class":82},[48,2474,2475],{"class":50,"line":929},[48,2476,2477],{"class":82},"    }\n",[48,2479,2480,2483,2485],{"class":50,"line":942},[48,2481,2482],{"class":82},"    $parts ",[48,2484,1877],{"class":69},[48,2486,2487],{"class":82}," [];\n",[48,2489,2490,2493,2495,2498,2500,2502,2505,2508],{"class":50,"line":962},[48,2491,2492],{"class":69},"    foreach",[48,2494,1345],{"class":82},[48,2496,2497],{"class":58},"explode",[48,2499,1327],{"class":82},[48,2501,1386],{"class":62},[48,2503,2504],{"class":82},", $header) ",[48,2506,2507],{"class":69},"as",[48,2509,2510],{"class":82}," $pair) {\n",[48,2512,2513,2516,2518,2521,2523,2525,2527,2529,2532,2535,2538,2540,2542,2544],{"class":50,"line":1003},[48,2514,2515],{"class":82},"        [$k, $v] ",[48,2517,1877],{"class":69},[48,2519,2520],{"class":58}," array_pad",[48,2522,1327],{"class":82},[48,2524,2497],{"class":58},[48,2526,1327],{"class":82},[48,2528,1413],{"class":62},[48,2530,2531],{"class":82},", $pair, ",[48,2533,2534],{"class":58},"2",[48,2536,2537],{"class":82},"), ",[48,2539,2534],{"class":58},[48,2541,184],{"class":82},[48,2543,872],{"class":58},[48,2545,1551],{"class":82},[48,2547,2548,2551,2553],{"class":50,"line":1009},[48,2549,2550],{"class":82},"        $parts[$k] ",[48,2552,1877],{"class":69},[48,2554,2555],{"class":82}," $v;\n",[48,2557,2558],{"class":50,"line":1715},[48,2559,2477],{"class":82},[48,2561,2562,2564,2566,2569,2572,2575,2578,2580,2583,2585,2588],{"class":50,"line":1747},[48,2563,1947],{"class":69},[48,2565,1345],{"class":82},[48,2567,2568],{"class":58},"empty",[48,2570,2571],{"class":82},"($parts[",[48,2573,2574],{"class":62},"'t'",[48,2576,2577],{"class":82},"]) ",[48,2579,1430],{"class":69},[48,2581,2582],{"class":58}," empty",[48,2584,2571],{"class":82},[48,2586,2587],{"class":62},"'v1'",[48,2589,2590],{"class":82},"])) {\n",[48,2592,2593,2595,2597],{"class":50,"line":1759},[48,2594,1958],{"class":69},[48,2596,1357],{"class":58},[48,2598,1251],{"class":82},[48,2600,2601],{"class":50,"line":1778},[48,2602,2477],{"class":82},[48,2604,2605,2607,2609,2611,2613,2616,2619,2622,2624,2626,2628,2630,2632,2634,2636,2638,2640,2643,2645,2647,2649,2652],{"class":50,"line":1784},[48,2606,1947],{"class":69},[48,2608,1345],{"class":82},[48,2610,1452],{"class":58},[48,2612,1395],{"class":82},[48,2614,2615],{"class":69},"int",[48,2617,2618],{"class":82},") (",[48,2620,2621],{"class":58},"microtime",[48,2623,1327],{"class":82},[48,2625,236],{"class":58},[48,2627,1400],{"class":82},[48,2629,288],{"class":69},[48,2631,2072],{"class":58},[48,2633,1400],{"class":82},[48,2635,1464],{"class":69},[48,2637,1345],{"class":82},[48,2639,2615],{"class":69},[48,2641,2642],{"class":82},") $parts[",[48,2644,2574],{"class":62},[48,2646,2577],{"class":82},[48,2648,1473],{"class":69},[48,2650,2651],{"class":58}," 300000",[48,2653,1337],{"class":82},[48,2655,2656,2658,2660,2662],{"class":50,"line":1789},[48,2657,1958],{"class":69},[48,2659,1357],{"class":58},[48,2661,1485],{"class":82},[48,2663,1488],{"class":1315},[48,2665,2666],{"class":50,"line":2157},[48,2667,2477],{"class":82},[48,2669,2670,2673,2675,2678,2680,2682,2685,2687,2690,2692,2695,2697],{"class":50,"line":2204},[48,2671,2672],{"class":82},"    $expected ",[48,2674,1877],{"class":69},[48,2676,2677],{"class":58}," hash_hmac",[48,2679,1327],{"class":82},[48,2681,1508],{"class":62},[48,2683,2684],{"class":82},", $parts[",[48,2686,2574],{"class":62},[48,2688,2689],{"class":82},"] ",[48,2691,434],{"class":69},[48,2693,2694],{"class":62}," '.'",[48,2696,2341],{"class":69},[48,2698,2699],{"class":82}," $raw, $secret);\n",[48,2701,2702,2704,2707,2710,2712],{"class":50,"line":2218},[48,2703,2131],{"class":69},[48,2705,2706],{"class":58}," hash_equals",[48,2708,2709],{"class":82},"($expected, $parts[",[48,2711,2587],{"class":62},[48,2713,2714],{"class":82},"]);\n",[48,2716,2717],{"class":50,"line":2228},[48,2718,267],{"class":82},[48,2720,2721],{"class":50,"line":2256},[48,2722,1271],{"emptyLinePlaceholder":1270},[48,2724,2725,2728,2730,2733,2736,2739],{"class":50,"line":2269},[48,2726,2727],{"class":82},"$app ",[48,2729,1877],{"class":69},[48,2731,2732],{"class":58}," AppFactory",[48,2734,2735],{"class":69},"::",[48,2737,2738],{"class":1285},"create",[48,2740,1289],{"class":82},[48,2742,2743,2746,2749,2751,2753,2755,2757,2759,2761,2764,2767,2770,2773,2775],{"class":50,"line":2290},[48,2744,2745],{"class":82},"$app",[48,2747,2748],{"class":69},"->",[48,2750,1638],{"class":1285},[48,2752,1327],{"class":82},[48,2754,1643],{"class":62},[48,2756,184],{"class":82},[48,2758,1321],{"class":69},[48,2760,1345],{"class":82},[48,2762,2763],{"class":58},"Request",[48,2765,2766],{"class":82}," $request, ",[48,2768,2769],{"class":58},"Response",[48,2771,2772],{"class":82}," $response) ",[48,2774,2355],{"class":69},[48,2776,2777],{"class":82}," ($secret) {\n",[48,2779,2781,2784,2786,2788,2790,2793,2795,2798],{"class":50,"line":2780},34,[48,2782,2783],{"class":82},"    $raw ",[48,2785,1877],{"class":69},[48,2787,1345],{"class":82},[48,2789,2430],{"class":69},[48,2791,2792],{"class":82},") $request",[48,2794,2748],{"class":69},[48,2796,2797],{"class":1285},"getBody",[48,2799,1289],{"class":82},[48,2801,2803,2805,2807,2809,2812,2815,2817,2820,2822,2824],{"class":50,"line":2802},35,[48,2804,1947],{"class":69},[48,2806,1345],{"class":82},[48,2808,1348],{"class":69},[48,2810,2811],{"class":1285},"wapito_verify",[48,2813,2814],{"class":82},"($raw, $request",[48,2816,2748],{"class":69},[48,2818,2819],{"class":1285},"getHeaderLine",[48,2821,1327],{"class":82},[48,2823,1694],{"class":62},[48,2825,2826],{"class":82},"), $secret)) {\n",[48,2828,2830,2832,2835,2837,2840,2842,2844],{"class":50,"line":2829},36,[48,2831,1958],{"class":69},[48,2833,2834],{"class":82}," $response",[48,2836,2748],{"class":69},[48,2838,2839],{"class":1285},"withStatus",[48,2841,1327],{"class":82},[48,2843,1710],{"class":58},[48,2845,1551],{"class":82},[48,2847,2849],{"class":50,"line":2848},37,[48,2850,2477],{"class":82},[48,2852,2854,2857,2859,2862,2865,2867],{"class":50,"line":2853},38,[48,2855,2856],{"class":82},"    $payload ",[48,2858,1877],{"class":69},[48,2860,2861],{"class":58}," json_decode",[48,2863,2864],{"class":82},"($raw, ",[48,2866,236],{"class":58},[48,2868,1551],{"class":82},[48,2870,2872,2875,2878,2881,2883,2885,2888,2890,2893,2895,2898],{"class":50,"line":2871},39,[48,2873,2874],{"class":58},"    error_log",[48,2876,2877],{"class":82},"($payload[",[48,2879,2880],{"class":62},"'event'",[48,2882,2689],{"class":82},[48,2884,434],{"class":69},[48,2886,2887],{"class":62}," ' '",[48,2889,2341],{"class":69},[48,2891,2892],{"class":58}," json_encode",[48,2894,2877],{"class":82},[48,2896,2897],{"class":62},"'data'",[48,2899,2900],{"class":82},"]));\n",[48,2902,2904,2906,2908,2910,2912,2914,2916,2918],{"class":50,"line":2903},40,[48,2905,2131],{"class":69},[48,2907,2834],{"class":82},[48,2909,2748],{"class":69},[48,2911,2839],{"class":1285},[48,2913,1327],{"class":82},[48,2915,1769],{"class":58},[48,2917,1772],{"class":82},[48,2919,1775],{"class":1315},[48,2921,2923],{"class":50,"line":2922},41,[48,2924,1781],{"class":82},[48,2926,2928,2930,2932,2935],{"class":50,"line":2927},42,[48,2929,2745],{"class":82},[48,2931,2748],{"class":69},[48,2933,2934],{"class":1285},"run",[48,2936,1289],{"class":82},[10,2938,2939,2940,2944],{},"Receivers in Java, C#, Go, Ruby, Kotlin and more are generated for every tutorial under\n",[724,2941,2943],{"href":2942},"\u002Fwhatsapp-bot\u002F","whatsapp-bot",", each with the same check.",[19,2946,2948],{"id":2947},"delivery-retries-and-back-off","Delivery, retries and back-off",[10,2950,2951,2952,2955,2956,2959,2960,2963],{},"A delivery succeeds when your endpoint answers any ",[14,2953,2954],{},"2xx"," within ",[27,2957,2958],{},"10 seconds"," — connection,\nheaders and body together. Anything else, including a timeout, a ",[14,2961,2962],{},"3xx"," or a TLS error, is a\nfailed attempt, and the job is retried on a fixed schedule:",[310,2965,2966,2976],{},[313,2967,2968],{},[316,2969,2970,2973],{},[319,2971,2972],{},"Failed attempts",[319,2974,2975],{},"Next attempt after",[329,2977,2978,2985,2992,3000,3008],{},[316,2979,2980,2982],{},[334,2981,1982],{},[334,2983,2984],{},"1 minute",[316,2986,2987,2989],{},[334,2988,2534],{},[334,2990,2991],{},"5 minutes",[316,2993,2994,2997],{},[334,2995,2996],{},"3",[334,2998,2999],{},"30 minutes",[316,3001,3002,3005],{},[334,3003,3004],{},"4",[334,3006,3007],{},"2 hours",[316,3009,3010,3013],{},[334,3011,3012],{},"5",[334,3014,3015,3016,3018],{},"— the job is marked ",[14,3017,417],{}," and not retried",[10,3020,3021,3022,3024,3025,3027,3028,3031],{},"Five attempts spread over roughly 2 hours 36 minutes, with the last wait of 12 hours reserved for\njobs whose attempt budget has been raised by hand. Retries carry the same ",[14,3023,1102],{},"\nvalue and the same event; each attempt is signed afresh, so ",[14,3026,1155],{}," and the signature change. Every attempt — status code, duration\nand the first 512 bytes of your response — is visible on the channel's ",[27,3029,3030],{},"Logs"," tab, which is\nthe first place to look when an event seems to be missing.",[10,3033,3034,3035,3038,3039,3041,3042,3044],{},"Delivery is ",[27,3036,3037],{},"at least once"," and not strictly ordered. Two deliveries can be in flight to the\nsame endpoint at once (the worker sends up to eight in parallel), and a retry can overtake a\nnewer event. Key your handler on the envelope ",[14,3040,518],{},", keep a short-lived set of ids you have already\nprocessed, and order by ",[14,3043,377],{}," when order matters.",[3046,3047,3049],"h3",{"id":3048},"the-circuit-breaker","The circuit breaker",[10,3051,3052,3053,3056,3057,3060],{},"An endpoint that is plainly down should not burn its jobs' five attempts. After ",[27,3054,3055],{},"five\nconsecutive failures"," to the same endpoint — scheme, host, port and path; the query string is\nignored — the circuit opens for ",[27,3058,3059],{},"60 seconds",". While it is open, jobs for that endpoint are put\nback on the queue with their attempt count untouched; after a minute one delivery is let through\nas a probe, and if it succeeds the circuit closes. An outage on your side therefore costs you\ntime, never events.",[3046,3062,3064],{"id":3063},"answer-fast-work-later","Answer fast, work later",[10,3066,3067,3068,3070],{},"Ten seconds is generous, and you should never come near it. Verify the signature, write the\npayload to a queue or a database, return ",[14,3069,1769],{},", and do the real work — replying, calling a CRM,\ndownloading media — from a worker. A handler that does its work inline will time out under\nload, be retried, and do the work twice.",[19,3072,3074],{"id":3073},"testing-an-endpoint","Testing an endpoint",[10,3076,3077,3081],{},[32,3078],{"label":3079,"op":3080,"tag":36},"POST \u002Fwebhooks\u002F{id}\u002Ftest","test-webhook"," sends one\nsynthetic event of the kind you name and reports what happened, without queueing or retrying:",[39,3083,3085],{"className":41,"code":3084,"language":43,"meta":44,"style":44},"curl -X POST https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fwebhooks\u002Fwhk_01JRQ8F4X9N2K7YB3C5V6W8H0T\u002Ftest \\\n  -H \"Authorization: Bearer $WAPITO_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"event\": \"messages\"}'\n",[14,3086,3087,3100,3112,3120],{"__ignoreMap":44},[48,3088,3089,3091,3093,3095,3098],{"class":50,"line":51},[48,3090,55],{"class":54},[48,3092,59],{"class":58},[48,3094,63],{"class":62},[48,3096,3097],{"class":62}," https:\u002F\u002Fapi.wapito.com\u002Fv1\u002Fwebhooks\u002Fwhk_01JRQ8F4X9N2K7YB3C5V6W8H0T\u002Ftest",[48,3099,70],{"class":69},[48,3101,3102,3104,3106,3108,3110],{"class":50,"line":73},[48,3103,76],{"class":58},[48,3105,79],{"class":62},[48,3107,83],{"class":82},[48,3109,86],{"class":62},[48,3111,70],{"class":69},[48,3113,3114,3116,3118],{"class":50,"line":91},[48,3115,76],{"class":58},[48,3117,96],{"class":62},[48,3119,70],{"class":69},[48,3121,3122,3124],{"class":50,"line":101},[48,3123,104],{"class":58},[48,3125,3126],{"class":62}," '{\"event\": \"messages\"}'\n",[39,3128,3130],{"className":134,"code":3129,"language":136,"meta":44,"style":44},"{\n  \"delivered\": true,\n  \"status_code\": 200,\n  \"duration_ms\": 148,\n  \"response_excerpt\": \"{\\\"ok\\\":true}\",\n  \"signature\": \"t=1789459200123,v1=5f8a1c0b…c4d5\",\n  \"error\": null\n}\n",[14,3131,3132,3136,3147,3158,3170,3193,3205,3215],{"__ignoreMap":44},[48,3133,3134],{"class":50,"line":51},[48,3135,143],{"class":82},[48,3137,3138,3141,3143,3145],{"class":50,"line":73},[48,3139,3140],{"class":148},"  \"delivered\"",[48,3142,152],{"class":82},[48,3144,236],{"class":58},[48,3146,158],{"class":82},[48,3148,3149,3152,3154,3156],{"class":50,"line":91},[48,3150,3151],{"class":148},"  \"status_code\"",[48,3153,152],{"class":82},[48,3155,1769],{"class":58},[48,3157,158],{"class":82},[48,3159,3160,3163,3165,3168],{"class":50,"line":101},[48,3161,3162],{"class":148},"  \"duration_ms\"",[48,3164,152],{"class":82},[48,3166,3167],{"class":58},"148",[48,3169,158],{"class":82},[48,3171,3172,3175,3177,3180,3183,3186,3188,3191],{"class":50,"line":110},[48,3173,3174],{"class":148},"  \"response_excerpt\"",[48,3176,152],{"class":82},[48,3178,3179],{"class":62},"\"{",[48,3181,3182],{"class":69},"\\\"",[48,3184,3185],{"class":62},"ok",[48,3187,3182],{"class":69},[48,3189,3190],{"class":62},":true}\"",[48,3192,158],{"class":82},[48,3194,3195,3198,3200,3203],{"class":50,"line":116},[48,3196,3197],{"class":148},"  \"signature\"",[48,3199,152],{"class":82},[48,3201,3202],{"class":62},"\"t=1789459200123,v1=5f8a1c0b…c4d5\"",[48,3204,158],{"class":82},[48,3206,3207,3210,3212],{"class":50,"line":122},[48,3208,3209],{"class":148},"  \"error\"",[48,3211,152],{"class":82},[48,3213,3214],{"class":58},"null\n",[48,3216,3217],{"class":50,"line":128},[48,3218,267],{"class":82},[10,3220,270,3221,3224],{},[14,3222,3223],{},"signature"," in the response is the header that was actually sent, so a verifier that rejects\nthe test can be debugged against a known-good value. The same button lives on the Settings tab of\nthe dashboard.",[3226,3227,3230],"callout",{"ban-risk":3228,"type":3229},"low","warn",[10,3231,3232,3233,3235,3236,3239],{},"Receiving events costs the linked number nothing — reading is the safe half of the API. The risk\nstarts with what your handler sends back: an auto-reply that fires on every ",[14,3234,338],{}," event,\nincluding the ones you sent yourself (",[14,3237,3238],{},"from_me: true","), is a loop, and a loop is the fastest\nway to make a number look like a machine.",[3241,3242,3243],"style",{},"html pre.shiki code .sTDnQ, html code.shiki .sTDnQ{--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHrmB, html code.shiki .sHrmB{--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sSVrQ, html code.shiki .sSVrQ{--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sjeE4, html code.shiki .sjeE4{--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .s4rv2, html code.shiki .s4rv2{--shiki-default:#1F2328;--shiki-dark:#E6EDF3}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);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sjgCt, html code.shiki .sjgCt{--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .sbjLL, html code.shiki .sbjLL{--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sU953, html code.shiki .sU953{--shiki-default:#6E7781;--shiki-dark:#8B949E}",{"title":44,"searchDepth":73,"depth":73,"links":3245},[3246,3247,3248,3249,3250,3251,3255],{"id":21,"depth":73,"text":22},{"id":307,"depth":73,"text":308},{"id":742,"depth":73,"text":743},{"id":1045,"depth":73,"text":1046},{"id":1137,"depth":73,"text":1138},{"id":2947,"depth":73,"text":2948,"children":3252},[3253,3254],{"id":3048,"depth":91,"text":3049},{"id":3063,"depth":91,"text":3064},{"id":3073,"depth":73,"text":3074},"2026-09-15","Subscribe to Wapito events, verify the HMAC signature, respond fast enough to avoid retries, and understand the delivery and back-off guarantees.","md",{},"\u002Fdocs\u002Fwebhooks",{"title":5,"description":3257},"docs\u002Fwebhooks",[36,307,3264],"security",null,"pFJ6LaqTgpYm_ABU6zoT92Yq2gCGkYkz5rjCe397fCI",1790464894220]