[{"data":1,"prerenderedAt":1435},["ShallowReactive",2],{"navigation":3,"\u002Fguides\u002Fsigned-webhooks":53,"\u002Fguides\u002Fsigned-webhooks-surround":1430},[4,13,28,46],{"title":5,"path":6,"stem":7,"children":8,"icon":12},"Getting Started","\u002Fgetting-started","1.getting-started\u002F1.index",[9],{"title":10,"path":6,"stem":7,"icon":11},"Concepts","i-lucide-house",false,{"title":14,"path":15,"stem":16,"children":17,"page":12},"Guides","\u002Fguides","2.guides",[18,23],{"title":19,"path":20,"stem":21,"icon":22},"Server webhooks","\u002Fguides\u002Fserver-webhooks","2.guides\u002F1.server-webhooks","i-lucide-server",{"title":24,"path":25,"stem":26,"icon":27},"Signed webhooks","\u002Fguides\u002Fsigned-webhooks","2.guides\u002F2.signed-webhooks","i-lucide-shield-check",{"title":29,"path":30,"stem":31,"children":32,"page":12},"Reference","\u002Freference","3.reference",[33,37,41],{"title":34,"path":35,"stem":36,"icon":22},"POST \u002Fapi\u002Fv1\u002Fingest","\u002Freference\u002Fingest","3.reference\u002F1.ingest",{"title":38,"path":39,"stem":40,"icon":27},"POST \u002Fapi\u002Fv1\u002Fingest\u002F:slug","\u002Freference\u002Fingest-slug","3.reference\u002F2.ingest-slug",{"title":42,"path":43,"stem":44,"icon":45},"Headers, errors & limits","\u002Freference\u002Fheaders-errors-limits","3.reference\u002F3.headers-errors-limits","i-lucide-list-checks",{"title":47,"path":48,"stem":49,"children":50,"icon":52},"Changelog","\u002Fchangelog","4.changelog\u002F1.index",[51],{"title":47,"path":48,"stem":49,"icon":52},"i-lucide-history",{"id":54,"title":24,"body":55,"description":1423,"extension":1424,"links":1425,"meta":1426,"navigation":1427,"path":25,"seo":1428,"sitemap":1425,"stem":26,"__hash__":1429},"docs\u002F2.guides\u002F2.signed-webhooks.md",{"type":56,"value":57,"toc":1415},"minimark",[58,62,67,78,94,101,105,172,175,195,199,204,208,1006,1010,1020,1395,1399,1411],[59,60,61],"p",{},"For webhook providers that need to sign requests rather than carry a bearer token — the URL itself is effectively public, so every request proves its authenticity with an HMAC signature instead.",[63,64,66],"h2",{"id":65},"endpoint","Endpoint",[68,69,74],"pre",{"className":70,"code":72,"language":73},[71],"language-text","POST https:\u002F\u002Fapp.blackglassleads.com\u002Fapi\u002Fv1\u002Fingest\u002F:slug\n","text",[75,76,72],"code",{"__ignoreMap":77},"",[59,79,80,83,84,93],{},[75,81,82],{},":slug"," is your form's slug, shown in ",[85,86,87,88,92],"strong",{},"Settings → Forms → ",[89,90,91],"span",{},"your form"," → Delivery",". That same Delivery tab is where you set (or rotate) the form's webhook secret and run the built-in signature debugger — paste a request you sent and it tells you exactly which byte range your signature was computed over vs. what arrived.",[59,95,96,97,100],{},"A webhook secret is ",[85,98,99],{},"required",". A form with no secret configured rejects every submission — there's no \"optional signature\" mode, because an unsigned public URL has no way to authenticate the caller at all.",[63,102,104],{"id":103},"headers","Headers",[106,107,108,121],"table",{},[109,110,111],"thead",{},[112,113,114,118],"tr",{},[115,116,117],"th",{},"Header",[115,119,120],{},"Value",[122,123,124,139,156],"tbody",{},[112,125,126,132],{},[127,128,129],"td",{},[75,130,131],{},"X-BGL-Timestamp",[127,133,134,135,138],{},"Unix seconds. Must be within ",[85,136,137],{},"±5 minutes"," of server time.",[112,140,141,146],{},[127,142,143],{},[75,144,145],{},"X-BGL-Signature",[127,147,148,151,152,155],{},[75,149,150],{},"HMAC-SHA256(secret, \"{timestamp}.{rawBody}\")",", lowercase hex. An optional ",[75,153,154],{},"sha256="," prefix is accepted (GitHub-style).",[112,157,158,163],{},[127,159,160],{},[75,161,162],{},"X-BGL-Idempotency-Key",[127,164,165,166,171],{},"Optional — same 24-hour dedup semantics as ",[167,168,170],"a",{"href":169},"\u002Fguides\u002Fserver-webhooks#idempotency","server webhooks",".",[59,173,174],{},"Two details that trip people up:",[176,177,178,185],"ul",{},[179,180,181,184],"li",{},[85,182,183],{},"Sign the raw bytes, not a re-serialized copy."," The signature covers the exact request body as sent. If your HTTP client re-encodes JSON before signing (different key order, different whitespace) the signature won't match what the server hashes, even though the \"same\" data was sent. Sign the literal string you're about to put on the wire.",[179,186,187,194],{},[85,188,189,190,193],{},"The signing input is ",[75,191,192],{},"\"{timestamp}.{rawBody}\"",", not just the body."," Binding the timestamp into the signature is what makes the replay window enforceable — a captured request can't be replayed against a different (later) timestamp, because that would need a new signature the attacker doesn't have.",[63,196,198],{"id":197},"why-the-timestamp-matters","Why the timestamp matters",[59,200,201,203],{},[75,202,131],{}," isn't just a freshness hint — it's part of what's signed. A request more than five minutes off server clock is rejected regardless of whether the signature is otherwise valid, so a captured request-plus-signature pair stops being replayable five minutes after it was issued. Keep your server's clock synced (NTP); skew is measured against server time, not your last deploy.",[63,205,207],{"id":206},"sending-a-signed-request","Sending a signed request",[209,210,211,440,795,900],"code-group",{},[68,212,217],{"className":213,"code":214,"filename":215,"language":216,"meta":77,"style":77},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","SECRET=\"your-form-webhook-secret\"\nTS=$(date +%s)\nBODY='{\"email\":\"sarah@acme.com\",\"name\":\"Sarah Chen\"}'\nSIG=$(printf '%s.%s' \"$TS\" \"$BODY\" | openssl dgst -sha256 -hmac \"$SECRET\" | cut -d' ' -f2)\n\ncurl -X POST \"https:\u002F\u002Fapp.blackglassleads.com\u002Fapi\u002Fv1\u002Fingest\u002Fyour-form-slug\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -H \"X-BGL-Timestamp: $TS\" \\\n  -H \"X-BGL-Signature: $SIG\" \\\n  -d \"$BODY\"\n","cURL","bash",[75,218,219,241,260,277,351,358,380,395,411,428],{"__ignoreMap":77},[89,220,223,227,231,234,238],{"class":221,"line":222},"line",1,[89,224,226],{"class":225},"sTEyZ","SECRET",[89,228,230],{"class":229},"sMK4o","=",[89,232,233],{"class":229},"\"",[89,235,237],{"class":236},"sfazB","your-form-webhook-secret",[89,239,240],{"class":229},"\"\n",[89,242,244,247,250,254,257],{"class":221,"line":243},2,[89,245,246],{"class":225},"TS",[89,248,249],{"class":229},"=$(",[89,251,253],{"class":252},"sBMFI","date",[89,255,256],{"class":236}," +%s",[89,258,259],{"class":229},")\n",[89,261,263,266,268,271,274],{"class":221,"line":262},3,[89,264,265],{"class":225},"BODY",[89,267,230],{"class":229},[89,269,270],{"class":229},"'",[89,272,273],{"class":236},"{\"email\":\"sarah@acme.com\",\"name\":\"Sarah Chen\"}",[89,275,276],{"class":229},"'\n",[89,278,280,283,285,289,292,295,297,300,303,305,307,310,312,315,318,321,324,327,329,332,334,336,339,342,344,346,349],{"class":221,"line":279},4,[89,281,282],{"class":225},"SIG",[89,284,249],{"class":229},[89,286,288],{"class":287},"s2Zo4","printf",[89,290,291],{"class":229}," '",[89,293,294],{"class":236},"%s.%s",[89,296,270],{"class":229},[89,298,299],{"class":229}," \"",[89,301,302],{"class":225},"$TS",[89,304,233],{"class":229},[89,306,299],{"class":229},[89,308,309],{"class":225},"$BODY",[89,311,233],{"class":229},[89,313,314],{"class":229}," |",[89,316,317],{"class":252}," openssl",[89,319,320],{"class":236}," dgst",[89,322,323],{"class":236}," -sha256",[89,325,326],{"class":236}," -hmac",[89,328,299],{"class":229},[89,330,331],{"class":225},"$SECRET",[89,333,233],{"class":229},[89,335,314],{"class":229},[89,337,338],{"class":252}," cut",[89,340,341],{"class":236}," -d",[89,343,270],{"class":229},[89,345,291],{"class":229},[89,347,348],{"class":236}," -f2",[89,350,259],{"class":229},[89,352,354],{"class":221,"line":353},5,[89,355,357],{"emptyLinePlaceholder":356},true,"\n",[89,359,361,364,367,370,372,375,377],{"class":221,"line":360},6,[89,362,363],{"class":252},"curl",[89,365,366],{"class":236}," -X",[89,368,369],{"class":236}," POST",[89,371,299],{"class":229},[89,373,374],{"class":236},"https:\u002F\u002Fapp.blackglassleads.com\u002Fapi\u002Fv1\u002Fingest\u002Fyour-form-slug",[89,376,233],{"class":229},[89,378,379],{"class":225}," \\\n",[89,381,383,386,388,391,393],{"class":221,"line":382},7,[89,384,385],{"class":236},"  -H",[89,387,299],{"class":229},[89,389,390],{"class":236},"Content-Type: application\u002Fjson",[89,392,233],{"class":229},[89,394,379],{"class":225},[89,396,398,400,402,405,407,409],{"class":221,"line":397},8,[89,399,385],{"class":236},[89,401,299],{"class":229},[89,403,404],{"class":236},"X-BGL-Timestamp: ",[89,406,302],{"class":225},[89,408,233],{"class":229},[89,410,379],{"class":225},[89,412,414,416,418,421,424,426],{"class":221,"line":413},9,[89,415,385],{"class":236},[89,417,299],{"class":229},[89,419,420],{"class":236},"X-BGL-Signature: ",[89,422,423],{"class":225},"$SIG",[89,425,233],{"class":229},[89,427,379],{"class":225},[89,429,431,434,436,438],{"class":221,"line":430},10,[89,432,433],{"class":236},"  -d",[89,435,299],{"class":229},[89,437,309],{"class":225},[89,439,240],{"class":229},[68,441,446],{"className":442,"code":443,"filename":444,"language":445,"meta":77,"style":77},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { createHmac } from 'node:crypto'\n\nconst secret = process.env.BGL_WEBHOOK_SECRET\nconst body = JSON.stringify({ email: 'sarah@acme.com', name: 'Sarah Chen' })\nconst ts = Math.floor(Date.now() \u002F 1000).toString()\nconst sig = createHmac('sha256', secret).update(`${ts}.${body}`).digest('hex')\n\nawait fetch('https:\u002F\u002Fapp.blackglassleads.com\u002Fapi\u002Fv1\u002Fingest\u002Fyour-form-slug', {\n  method: 'POST',\n  headers: {\n    'Content-Type': 'application\u002Fjson',\n    'X-BGL-Timestamp': ts,\n    'X-BGL-Signature': sig\n  },\n  body\n})\n","Node (fetch + crypto)","js",[75,447,448,473,477,501,557,603,673,677,698,715,724,746,762,776,782,788],{"__ignoreMap":77},[89,449,450,454,457,460,463,466,468,471],{"class":221,"line":222},[89,451,453],{"class":452},"s7zQu","import",[89,455,456],{"class":229}," {",[89,458,459],{"class":225}," createHmac",[89,461,462],{"class":229}," }",[89,464,465],{"class":452}," from",[89,467,291],{"class":229},[89,469,470],{"class":236},"node:crypto",[89,472,276],{"class":229},[89,474,475],{"class":221,"line":243},[89,476,357],{"emptyLinePlaceholder":356},[89,478,479,483,486,488,491,493,496,498],{"class":221,"line":262},[89,480,482],{"class":481},"spNyl","const",[89,484,485],{"class":225}," secret ",[89,487,230],{"class":229},[89,489,490],{"class":225}," process",[89,492,171],{"class":229},[89,494,495],{"class":225},"env",[89,497,171],{"class":229},[89,499,500],{"class":225},"BGL_WEBHOOK_SECRET\n",[89,502,503,505,508,510,513,515,518,521,524,528,531,533,536,538,541,544,546,548,551,553,555],{"class":221,"line":279},[89,504,482],{"class":481},[89,506,507],{"class":225}," body ",[89,509,230],{"class":229},[89,511,512],{"class":225}," JSON",[89,514,171],{"class":229},[89,516,517],{"class":287},"stringify",[89,519,520],{"class":225},"(",[89,522,523],{"class":229},"{",[89,525,527],{"class":526},"swJcz"," email",[89,529,530],{"class":229},":",[89,532,291],{"class":229},[89,534,535],{"class":236},"sarah@acme.com",[89,537,270],{"class":229},[89,539,540],{"class":229},",",[89,542,543],{"class":526}," name",[89,545,530],{"class":229},[89,547,291],{"class":229},[89,549,550],{"class":236},"Sarah Chen",[89,552,270],{"class":229},[89,554,462],{"class":229},[89,556,259],{"class":225},[89,558,559,561,564,566,569,571,574,577,579,582,585,588,592,595,597,600],{"class":221,"line":353},[89,560,482],{"class":481},[89,562,563],{"class":225}," ts ",[89,565,230],{"class":229},[89,567,568],{"class":225}," Math",[89,570,171],{"class":229},[89,572,573],{"class":287},"floor",[89,575,576],{"class":225},"(Date",[89,578,171],{"class":229},[89,580,581],{"class":287},"now",[89,583,584],{"class":225},"() ",[89,586,587],{"class":229},"\u002F",[89,589,591],{"class":590},"sbssI"," 1000",[89,593,594],{"class":225},")",[89,596,171],{"class":229},[89,598,599],{"class":287},"toString",[89,601,602],{"class":225},"()\n",[89,604,605,607,610,612,614,616,618,621,623,625,628,630,633,635,638,641,644,646,649,652,655,657,659,662,664,666,669,671],{"class":221,"line":360},[89,606,482],{"class":481},[89,608,609],{"class":225}," sig ",[89,611,230],{"class":229},[89,613,459],{"class":287},[89,615,520],{"class":225},[89,617,270],{"class":229},[89,619,620],{"class":236},"sha256",[89,622,270],{"class":229},[89,624,540],{"class":229},[89,626,627],{"class":225}," secret)",[89,629,171],{"class":229},[89,631,632],{"class":287},"update",[89,634,520],{"class":225},[89,636,637],{"class":229},"`${",[89,639,640],{"class":225},"ts",[89,642,643],{"class":229},"}",[89,645,171],{"class":236},[89,647,648],{"class":229},"${",[89,650,651],{"class":225},"body",[89,653,654],{"class":229},"}`",[89,656,594],{"class":225},[89,658,171],{"class":229},[89,660,661],{"class":287},"digest",[89,663,520],{"class":225},[89,665,270],{"class":229},[89,667,668],{"class":236},"hex",[89,670,270],{"class":229},[89,672,259],{"class":225},[89,674,675],{"class":221,"line":382},[89,676,357],{"emptyLinePlaceholder":356},[89,678,679,682,685,687,689,691,693,695],{"class":221,"line":397},[89,680,681],{"class":452},"await",[89,683,684],{"class":287}," fetch",[89,686,520],{"class":225},[89,688,270],{"class":229},[89,690,374],{"class":236},[89,692,270],{"class":229},[89,694,540],{"class":229},[89,696,697],{"class":229}," {\n",[89,699,700,703,705,707,710,712],{"class":221,"line":413},[89,701,702],{"class":526},"  method",[89,704,530],{"class":229},[89,706,291],{"class":229},[89,708,709],{"class":236},"POST",[89,711,270],{"class":229},[89,713,714],{"class":229},",\n",[89,716,717,720,722],{"class":221,"line":430},[89,718,719],{"class":526},"  headers",[89,721,530],{"class":229},[89,723,697],{"class":229},[89,725,727,730,733,735,737,739,742,744],{"class":221,"line":726},11,[89,728,729],{"class":229},"    '",[89,731,732],{"class":526},"Content-Type",[89,734,270],{"class":229},[89,736,530],{"class":229},[89,738,291],{"class":229},[89,740,741],{"class":236},"application\u002Fjson",[89,743,270],{"class":229},[89,745,714],{"class":229},[89,747,749,751,753,755,757,760],{"class":221,"line":748},12,[89,750,729],{"class":229},[89,752,131],{"class":526},[89,754,270],{"class":229},[89,756,530],{"class":229},[89,758,759],{"class":225}," ts",[89,761,714],{"class":229},[89,763,765,767,769,771,773],{"class":221,"line":764},13,[89,766,729],{"class":229},[89,768,145],{"class":526},[89,770,270],{"class":229},[89,772,530],{"class":229},[89,774,775],{"class":225}," sig\n",[89,777,779],{"class":221,"line":778},14,[89,780,781],{"class":229},"  },\n",[89,783,785],{"class":221,"line":784},15,[89,786,787],{"class":225},"  body\n",[89,789,791,793],{"class":221,"line":790},16,[89,792,643],{"class":229},[89,794,259],{"class":225},[68,796,801],{"className":797,"code":798,"filename":799,"language":800,"meta":77,"style":77},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C?php\n$secret = getenv('BGL_WEBHOOK_SECRET');\n$body = json_encode(['email' => 'sarah@acme.com', 'name' => 'Sarah Chen']);\n$ts = (string) time();\n$sig = hash_hmac('sha256', $ts . '.' . $body, $secret);\n\n$ch = curl_init('https:\u002F\u002Fapp.blackglassleads.com\u002Fapi\u002Fv1\u002Fingest\u002Fyour-form-slug');\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_POSTFIELDS => $body,\n    CURLOPT_HTTPHEADER => [\n        'Content-Type: application\u002Fjson',\n        \"X-BGL-Timestamp: $ts\",\n        \"X-BGL-Signature: $sig\",\n    ],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n$response = curl_exec($ch);\ncurl_close($ch);\n","PHP","php",[75,802,803,808,813,818,823,828,832,837,842,847,852,857,862,867,872,877,882,888,894],{"__ignoreMap":77},[89,804,805],{"class":221,"line":222},[89,806,807],{},"\u003C?php\n",[89,809,810],{"class":221,"line":243},[89,811,812],{},"$secret = getenv('BGL_WEBHOOK_SECRET');\n",[89,814,815],{"class":221,"line":262},[89,816,817],{},"$body = json_encode(['email' => 'sarah@acme.com', 'name' => 'Sarah Chen']);\n",[89,819,820],{"class":221,"line":279},[89,821,822],{},"$ts = (string) time();\n",[89,824,825],{"class":221,"line":353},[89,826,827],{},"$sig = hash_hmac('sha256', $ts . '.' . $body, $secret);\n",[89,829,830],{"class":221,"line":360},[89,831,357],{"emptyLinePlaceholder":356},[89,833,834],{"class":221,"line":382},[89,835,836],{},"$ch = curl_init('https:\u002F\u002Fapp.blackglassleads.com\u002Fapi\u002Fv1\u002Fingest\u002Fyour-form-slug');\n",[89,838,839],{"class":221,"line":397},[89,840,841],{},"curl_setopt_array($ch, [\n",[89,843,844],{"class":221,"line":413},[89,845,846],{},"    CURLOPT_POST => true,\n",[89,848,849],{"class":221,"line":430},[89,850,851],{},"    CURLOPT_POSTFIELDS => $body,\n",[89,853,854],{"class":221,"line":726},[89,855,856],{},"    CURLOPT_HTTPHEADER => [\n",[89,858,859],{"class":221,"line":748},[89,860,861],{},"        'Content-Type: application\u002Fjson',\n",[89,863,864],{"class":221,"line":764},[89,865,866],{},"        \"X-BGL-Timestamp: $ts\",\n",[89,868,869],{"class":221,"line":778},[89,870,871],{},"        \"X-BGL-Signature: $sig\",\n",[89,873,874],{"class":221,"line":784},[89,875,876],{},"    ],\n",[89,878,879],{"class":221,"line":790},[89,880,881],{},"    CURLOPT_RETURNTRANSFER => true,\n",[89,883,885],{"class":221,"line":884},17,[89,886,887],{},"]);\n",[89,889,891],{"class":221,"line":890},18,[89,892,893],{},"$response = curl_exec($ch);\n",[89,895,897],{"class":221,"line":896},19,[89,898,899],{},"curl_close($ch);\n",[68,901,906],{"className":902,"code":903,"filename":904,"language":905,"meta":77,"style":77},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import hmac\nimport hashlib\nimport time\nimport json\nimport requests\n\nsecret = \"your-form-webhook-secret\"\nbody = json.dumps({\"email\": \"sarah@acme.com\", \"name\": \"Sarah Chen\"})\nts = str(int(time.time()))\nsig = hmac.new(secret.encode(), f\"{ts}.{body}\".encode(), hashlib.sha256).hexdigest()\n\nresponse = requests.post(\n    \"https:\u002F\u002Fapp.blackglassleads.com\u002Fapi\u002Fv1\u002Fingest\u002Fyour-form-slug\",\n    headers={\n        \"Content-Type\": \"application\u002Fjson\",\n        \"X-BGL-Timestamp\": ts,\n        \"X-BGL-Signature\": sig,\n    },\n    data=body,\n)\n","Python","python",[75,907,908,913,918,923,928,933,937,942,947,952,957,961,966,971,976,981,986,991,996,1001],{"__ignoreMap":77},[89,909,910],{"class":221,"line":222},[89,911,912],{},"import hmac\n",[89,914,915],{"class":221,"line":243},[89,916,917],{},"import hashlib\n",[89,919,920],{"class":221,"line":262},[89,921,922],{},"import time\n",[89,924,925],{"class":221,"line":279},[89,926,927],{},"import json\n",[89,929,930],{"class":221,"line":353},[89,931,932],{},"import requests\n",[89,934,935],{"class":221,"line":360},[89,936,357],{"emptyLinePlaceholder":356},[89,938,939],{"class":221,"line":382},[89,940,941],{},"secret = \"your-form-webhook-secret\"\n",[89,943,944],{"class":221,"line":397},[89,945,946],{},"body = json.dumps({\"email\": \"sarah@acme.com\", \"name\": \"Sarah Chen\"})\n",[89,948,949],{"class":221,"line":413},[89,950,951],{},"ts = str(int(time.time()))\n",[89,953,954],{"class":221,"line":430},[89,955,956],{},"sig = hmac.new(secret.encode(), f\"{ts}.{body}\".encode(), hashlib.sha256).hexdigest()\n",[89,958,959],{"class":221,"line":726},[89,960,357],{"emptyLinePlaceholder":356},[89,962,963],{"class":221,"line":748},[89,964,965],{},"response = requests.post(\n",[89,967,968],{"class":221,"line":764},[89,969,970],{},"    \"https:\u002F\u002Fapp.blackglassleads.com\u002Fapi\u002Fv1\u002Fingest\u002Fyour-form-slug\",\n",[89,972,973],{"class":221,"line":778},[89,974,975],{},"    headers={\n",[89,977,978],{"class":221,"line":784},[89,979,980],{},"        \"Content-Type\": \"application\u002Fjson\",\n",[89,982,983],{"class":221,"line":790},[89,984,985],{},"        \"X-BGL-Timestamp\": ts,\n",[89,987,988],{"class":221,"line":884},[89,989,990],{},"        \"X-BGL-Signature\": sig,\n",[89,992,993],{"class":221,"line":890},[89,994,995],{},"    },\n",[89,997,998],{"class":221,"line":896},[89,999,1000],{},"    data=body,\n",[89,1002,1004],{"class":221,"line":1003},20,[89,1005,259],{},[63,1007,1009],{"id":1008},"verifying-a-signature-yourself","Verifying a signature yourself",[59,1011,1012,1013,587,1016,1019],{},"Useful when you're debugging a mismatch locally, or building something that needs to check a signature the same way we do — always with a constant-time comparison, never ",[75,1014,1015],{},"===",[75,1017,1018],{},"=="," on the raw strings.",[209,1021,1022,1307,1344],{},[68,1023,1026],{"className":442,"code":1024,"filename":1025,"language":445,"meta":77,"style":77},"import { createHmac, timingSafeEqual } from 'node:crypto'\n\nfunction isValidSignature(secret, timestamp, rawBody, signatureHeader) {\n  const expected = createHmac('sha256', secret)\n    .update(`${timestamp}.${rawBody}`)\n    .digest('hex')\n  const received = signatureHeader.replace(\u002F^sha256=\u002F, '').toLowerCase()\n\n  const a = Buffer.from(expected, 'hex')\n  const b = Buffer.from(received, 'hex')\n  return a.length === b.length && timingSafeEqual(a, b)\n}\n","Node (crypto)",[75,1027,1028,1051,1055,1088,1116,1143,1159,1200,1204,1236,1266,1302],{"__ignoreMap":77},[89,1029,1030,1032,1034,1036,1038,1041,1043,1045,1047,1049],{"class":221,"line":222},[89,1031,453],{"class":452},[89,1033,456],{"class":229},[89,1035,459],{"class":225},[89,1037,540],{"class":229},[89,1039,1040],{"class":225}," timingSafeEqual",[89,1042,462],{"class":229},[89,1044,465],{"class":452},[89,1046,291],{"class":229},[89,1048,470],{"class":236},[89,1050,276],{"class":229},[89,1052,1053],{"class":221,"line":243},[89,1054,357],{"emptyLinePlaceholder":356},[89,1056,1057,1060,1063,1065,1069,1071,1074,1076,1079,1081,1084,1086],{"class":221,"line":262},[89,1058,1059],{"class":481},"function",[89,1061,1062],{"class":287}," isValidSignature",[89,1064,520],{"class":229},[89,1066,1068],{"class":1067},"sHdIc","secret",[89,1070,540],{"class":229},[89,1072,1073],{"class":1067}," timestamp",[89,1075,540],{"class":229},[89,1077,1078],{"class":1067}," rawBody",[89,1080,540],{"class":229},[89,1082,1083],{"class":1067}," signatureHeader",[89,1085,594],{"class":229},[89,1087,697],{"class":229},[89,1089,1090,1093,1096,1099,1101,1103,1105,1107,1109,1111,1114],{"class":221,"line":279},[89,1091,1092],{"class":481},"  const",[89,1094,1095],{"class":225}," expected",[89,1097,1098],{"class":229}," =",[89,1100,459],{"class":287},[89,1102,520],{"class":526},[89,1104,270],{"class":229},[89,1106,620],{"class":236},[89,1108,270],{"class":229},[89,1110,540],{"class":229},[89,1112,1113],{"class":225}," secret",[89,1115,259],{"class":526},[89,1117,1118,1121,1123,1125,1127,1130,1132,1134,1136,1139,1141],{"class":221,"line":353},[89,1119,1120],{"class":229},"    .",[89,1122,632],{"class":287},[89,1124,520],{"class":526},[89,1126,637],{"class":229},[89,1128,1129],{"class":225},"timestamp",[89,1131,643],{"class":229},[89,1133,171],{"class":236},[89,1135,648],{"class":229},[89,1137,1138],{"class":225},"rawBody",[89,1140,654],{"class":229},[89,1142,259],{"class":526},[89,1144,1145,1147,1149,1151,1153,1155,1157],{"class":221,"line":360},[89,1146,1120],{"class":229},[89,1148,661],{"class":287},[89,1150,520],{"class":526},[89,1152,270],{"class":229},[89,1154,668],{"class":236},[89,1156,270],{"class":229},[89,1158,259],{"class":526},[89,1160,1161,1163,1166,1168,1170,1172,1175,1177,1179,1182,1184,1186,1188,1191,1193,1195,1198],{"class":221,"line":382},[89,1162,1092],{"class":481},[89,1164,1165],{"class":225}," received",[89,1167,1098],{"class":229},[89,1169,1083],{"class":225},[89,1171,171],{"class":229},[89,1173,1174],{"class":287},"replace",[89,1176,520],{"class":526},[89,1178,587],{"class":229},[89,1180,1181],{"class":452},"^",[89,1183,154],{"class":236},[89,1185,587],{"class":229},[89,1187,540],{"class":229},[89,1189,1190],{"class":229}," ''",[89,1192,594],{"class":526},[89,1194,171],{"class":229},[89,1196,1197],{"class":287},"toLowerCase",[89,1199,602],{"class":526},[89,1201,1202],{"class":221,"line":397},[89,1203,357],{"emptyLinePlaceholder":356},[89,1205,1206,1208,1211,1213,1216,1218,1221,1223,1226,1228,1230,1232,1234],{"class":221,"line":413},[89,1207,1092],{"class":481},[89,1209,1210],{"class":225}," a",[89,1212,1098],{"class":229},[89,1214,1215],{"class":225}," Buffer",[89,1217,171],{"class":229},[89,1219,1220],{"class":287},"from",[89,1222,520],{"class":526},[89,1224,1225],{"class":225},"expected",[89,1227,540],{"class":229},[89,1229,291],{"class":229},[89,1231,668],{"class":236},[89,1233,270],{"class":229},[89,1235,259],{"class":526},[89,1237,1238,1240,1243,1245,1247,1249,1251,1253,1256,1258,1260,1262,1264],{"class":221,"line":430},[89,1239,1092],{"class":481},[89,1241,1242],{"class":225}," b",[89,1244,1098],{"class":229},[89,1246,1215],{"class":225},[89,1248,171],{"class":229},[89,1250,1220],{"class":287},[89,1252,520],{"class":526},[89,1254,1255],{"class":225},"received",[89,1257,540],{"class":229},[89,1259,291],{"class":229},[89,1261,668],{"class":236},[89,1263,270],{"class":229},[89,1265,259],{"class":526},[89,1267,1268,1271,1273,1275,1278,1281,1283,1285,1287,1290,1292,1294,1296,1298,1300],{"class":221,"line":726},[89,1269,1270],{"class":452},"  return",[89,1272,1210],{"class":225},[89,1274,171],{"class":229},[89,1276,1277],{"class":225},"length",[89,1279,1280],{"class":229}," ===",[89,1282,1242],{"class":225},[89,1284,171],{"class":229},[89,1286,1277],{"class":225},[89,1288,1289],{"class":229}," &&",[89,1291,1040],{"class":287},[89,1293,520],{"class":526},[89,1295,167],{"class":225},[89,1297,540],{"class":229},[89,1299,1242],{"class":225},[89,1301,259],{"class":526},[89,1303,1304],{"class":221,"line":748},[89,1305,1306],{"class":229},"}\n",[68,1308,1310],{"className":797,"code":1309,"filename":799,"language":800,"meta":77,"style":77},"\u003C?php\nfunction is_valid_signature(string $secret, string $timestamp, string $rawBody, string $signatureHeader): bool {\n    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);\n    $received = strtolower(preg_replace('\u002F^sha256=\u002F', '', $signatureHeader));\n\n    return hash_equals($expected, $received);\n}\n",[75,1311,1312,1316,1321,1326,1331,1335,1340],{"__ignoreMap":77},[89,1313,1314],{"class":221,"line":222},[89,1315,807],{},[89,1317,1318],{"class":221,"line":243},[89,1319,1320],{},"function is_valid_signature(string $secret, string $timestamp, string $rawBody, string $signatureHeader): bool {\n",[89,1322,1323],{"class":221,"line":262},[89,1324,1325],{},"    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);\n",[89,1327,1328],{"class":221,"line":279},[89,1329,1330],{},"    $received = strtolower(preg_replace('\u002F^sha256=\u002F', '', $signatureHeader));\n",[89,1332,1333],{"class":221,"line":353},[89,1334,357],{"emptyLinePlaceholder":356},[89,1336,1337],{"class":221,"line":360},[89,1338,1339],{},"    return hash_equals($expected, $received);\n",[89,1341,1342],{"class":221,"line":382},[89,1343,1306],{},[68,1345,1347],{"className":902,"code":1346,"filename":904,"language":905,"meta":77,"style":77},"import hmac\nimport hashlib\n\ndef is_valid_signature(secret: str, timestamp: str, raw_body: str, signature_header: str) -> bool:\n    expected = hmac.new(\n        secret.encode(), f\"{timestamp}.{raw_body}\".encode(), hashlib.sha256\n    ).hexdigest()\n    received = signature_header.removeprefix(\"sha256=\").lower()\n\n    return hmac.compare_digest(expected, received)\n",[75,1348,1349,1353,1357,1361,1366,1371,1376,1381,1386,1390],{"__ignoreMap":77},[89,1350,1351],{"class":221,"line":222},[89,1352,912],{},[89,1354,1355],{"class":221,"line":243},[89,1356,917],{},[89,1358,1359],{"class":221,"line":262},[89,1360,357],{"emptyLinePlaceholder":356},[89,1362,1363],{"class":221,"line":279},[89,1364,1365],{},"def is_valid_signature(secret: str, timestamp: str, raw_body: str, signature_header: str) -> bool:\n",[89,1367,1368],{"class":221,"line":353},[89,1369,1370],{},"    expected = hmac.new(\n",[89,1372,1373],{"class":221,"line":360},[89,1374,1375],{},"        secret.encode(), f\"{timestamp}.{raw_body}\".encode(), hashlib.sha256\n",[89,1377,1378],{"class":221,"line":382},[89,1379,1380],{},"    ).hexdigest()\n",[89,1382,1383],{"class":221,"line":397},[89,1384,1385],{},"    received = signature_header.removeprefix(\"sha256=\").lower()\n",[89,1387,1388],{"class":221,"line":413},[89,1389,357],{"emptyLinePlaceholder":356},[89,1391,1392],{"class":221,"line":430},[89,1393,1394],{},"    return hmac.compare_digest(expected, received)\n",[63,1396,1398],{"id":1397},"idempotency-rate-limits","Idempotency & rate limits",[59,1400,1401,1402,1404,1405,1407,1408,1410],{},"Same as ",[167,1403,170],{"href":20},": an optional ",[75,1406,162],{}," header dedupes retries for 24 hours, and the workspace-level limits are 1,000 submissions\u002Fminute and 50,000\u002Fday, tracked in a separate bucket from Surface 1a so a burst on one surface doesn't trip the other. See ",[167,1409,42],{"href":43}," for the complete rejection-reason and status-code reference.",[1412,1413,1414],"style",{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":77,"searchDepth":222,"depth":243,"links":1416},[1417,1418,1419,1420,1421,1422],{"id":65,"depth":243,"text":66},{"id":103,"depth":243,"text":104},{"id":197,"depth":243,"text":198},{"id":206,"depth":243,"text":207},{"id":1008,"depth":243,"text":1009},{"id":1397,"depth":243,"text":1398},"HMAC-SHA256 signed submission with mandatory secrets and replay protection — Surface 1b.","md",null,{},{"icon":27},{"title":24,"description":1423},"qVKk9_TJ2QAXbV7ELg986TuEmeVxDoOkNjvCSpLZ1Yg",[1431,1433],{"title":19,"path":20,"stem":21,"description":1432,"icon":22,"children":-1},"Bearer-token authentication for server-to-server lead submission — Surface 1a.",{"title":34,"path":35,"stem":36,"description":1434,"icon":22,"children":-1},"Reference for the Bearer-token server ingest endpoint — Surface 1a.",1786814658154]