[{"data":1,"prerenderedAt":5852},["ShallowReactive",2],{"navigation":3,"page:\u002Fguides\u002Fwebhooks":1019,"surround:\u002Fguides\u002Fwebhooks":5843},[4,21,82,133,154,1010],[5,6,7,8],"Getting started","\u002Fgetting-started","i-lucide-rocket",[9,12,15,18],[10,6,11,11],"What Kaabist is",0,[13,14,11,11],"API keys and modes","keys-and-modes",[16,17,11,11],"Your first job","first-job",[19,20,11,11],"Understanding the envelope","envelope",[22,23,24,25],"Concepts","\u002Fconcepts","i-lucide-book-open",[26,28,31,34,37,40,43,46,49,52,55,58,61,64,67,70,73,76,79],[27,23,11,11],"Overview",[29,30,11,11],"Programs and versions","programs-and-versions",[32,33,11,11],"Tiers and escalation","tiers-and-escalation",[35,36,11,11],"The http tier","http-tier",[38,39,11,11],"The browser tier","browser-tier",[41,42,11,11],"Sessions","sessions",[44,45,11,11],"Human in the loop","human-in-the-loop",[47,48,11,11],"How healing works","self-healing",[50,51,11,11],"Obstacles and captchas","obstacles",[53,54,11,11],"Validation and warnings","validation-and-warnings",[56,57,11,11],"Credits and billing","credits-and-billing",[59,60,11,11],"Limits","limits",[62,63,11,11],"Locators","locators",[65,66,11,11],"Snapshots","snapshots",[68,69,11,11],"Egress and proxies","egress-and-proxies",[71,72,11,11],"Queueing and fairness","queueing",[74,75,11,11],"Accounts, organisations and roles","accounts-and-roles",[77,78,11,11],"Data retention","data-retention",[80,81,11,11],"Documents and PDFs","documents-and-pdfs",[83,84,85,86],"Guides","\u002Fguides","i-lucide-map",[87,88,91,94,97,100,103,106,109,112,115,118,121,124,127,130],[27,84,11,11],[89,90,11,11],"Authenticated flows","authenticated-flows",[92,93,11,11],"Hosted resolve pages","hosted-resolve",[95,96,11,11],"Lists and pagination","lists-and-pagination",[98,99,11,11],"JSON and network locators","json-and-network-locators",[101,102,11,11],"Schedules and change detection","schedules-and-change-detection",[104,105,11,11],"Webhooks","webhooks",[107,108,11,11],"Bring your own providers","byo-providers",[110,111,11,11],"Testing: replay, dry-run and test mode","testing",[113,114,11,11],"Side-effect safety","side-effect-safety",[116,117,11,11],"Security & data handling","security-and-data",[119,120,11,11],"Migrating from other tools","migrating",[122,123,11,11],"Drift detection and canaries","drift-and-canaries",[125,126,11,11],"SDKs","sdks",[128,129,11,11],"The kaab CLI","cli",[131,132,11,11],"Coding agents (MCP)","mcp",[134,135,136,137],"Panel","\u002Fpanel","i-lucide-layout-dashboard",[138,139,142,145,148,151],[27,135,11,11],[140,141,11,11],"The program builder","builder",[143,144,11,11],"Picking and recording","picking-and-recording",[146,147,11,11],"Taking over a job","takeover",[149,150,11,11],"Getting help","support",[152,153,11,11],"Referrals","referrals",[155,156,157,158],"Reference","\u002Freference","i-lucide-library",[159,160,520,834,837,840,1006,1007],[27,156,11,11],[161,162,11,163],"API","api",[164,166,169,212,222,274,297,316,347,376,389,396,403,410,426,447,454],[27,165,11,11],"\u002Freference\u002Fapi",[167,168,11,11],"Interactive reference","explorer",[170,171,11,172],"Jobs","jobs",[173,176,179,182,185,188,191,194,197,200,203,206,209],[174,175,11,11],"Create job","create-job",[177,178,11,11],"List jobs","list-jobs",[180,181,11,11],"Create job batch","create-job-batch",[183,184,11,11],"Estimate job","estimate-job",[186,187,11,11],"Get job","get-job",[189,190,11,11],"List job steps","list-job-steps",[192,193,11,11],"Get job snapshot","get-job-snapshot",[195,196,11,11],"Get job screenshot","get-job-screenshot",[198,199,11,11],"Get job artifact","get-job-artifact",[201,202,11,11],"Submit job input","submit-job-input",[204,205,11,11],"Resume job","resume-job",[207,208,11,11],"Takeover job","takeover-job",[210,211,11,11],"Cancel job","cancel-job",[213,214,11,215],"Batches","batches",[216,219],[217,218,11,11],"Get batch","get-batch",[220,221,11,11],"List batch jobs","list-batch-jobs",[223,224,11,225],"Programs","programs",[226,229,232,235,238,241,244,247,250,253,256,259,262,265,268,271],[227,228,11,11],"Create program","create-program",[230,231,11,11],"List programs","list-programs",[233,234,11,11],"Lint program","lint-program",[236,237,11,11],"Get program","get-program",[239,240,11,11],"Update program","update-program",[242,243,11,11],"Delete program","delete-program",[245,246,11,11],"List program versions","list-program-versions",[248,249,11,11],"Create program version","create-program-version",[251,252,11,11],"Update program version","update-program-version",[254,255,11,11],"Delete program version","delete-program-version",[257,258,11,11],"Get program version","get-program-version",[260,261,11,11],"Publish program version","publish-program-version",[263,264,11,11],"Diff program version","diff-program-version",[266,267,11,11],"Replay program version","replay-program-version",[269,270,11,11],"Set program alias","set-program-alias",[272,273,11,11],"Delete program alias","delete-program-alias",[41,42,11,275],[276,279,282,285,288,291,294],[277,278,11,11],"Create session","create-session",[280,281,11,11],"List sessions","list-sessions",[283,284,11,11],"Get session","get-session",[286,287,11,11],"Update session","update-session",[289,290,11,11],"Delete session","delete-session",[292,293,11,11],"Verify session","verify-session",[295,296,11,11],"Refresh session","refresh-session",[298,299,11,300],"Proposals","proposals",[301,304,307,310,313],[302,303,11,11],"List proposals","list-proposals",[305,306,11,11],"Get proposal","get-proposal",[308,309,11,11],"Approve proposal","approve-proposal",[311,312,11,11],"Reject proposal","reject-proposal",[314,315,11,11],"Edit proposal","edit-proposal",[317,318,11,319],"Schedules","schedules",[320,323,326,329,332,335,338,341,344],[321,322,11,11],"Create schedule","create-schedule",[324,325,11,11],"List schedules","list-schedules",[327,328,11,11],"Get schedule","get-schedule",[330,331,11,11],"Update schedule","update-schedule",[333,334,11,11],"Delete schedule","delete-schedule",[336,337,11,11],"Pause schedule","pause-schedule",[339,340,11,11],"Resume schedule","resume-schedule",[342,343,11,11],"List schedule runs","list-schedule-runs",[345,346,11,11],"Get schedule run diff","get-schedule-run-diff",[104,105,11,348],[349,352,355,358,361,364,367,370,373],[350,351,11,11],"Create webhook","create-webhook",[353,354,11,11],"List webhooks","list-webhooks",[356,357,11,11],"Get webhook","get-webhook",[359,360,11,11],"Update webhook","update-webhook",[362,363,11,11],"Delete webhook","delete-webhook",[365,366,11,11],"Rotate webhook secret","rotate-webhook-secret",[368,369,11,11],"List webhook deliveries","list-webhook-deliveries",[371,372,11,11],"Resend webhook delivery","resend-webhook-delivery",[374,375,11,11],"Test webhook","test-webhook",[377,378,11,379],"Organisation","org",[380,383,386],[381,382,11,11],"Rotate org webhook secret","rotate-org-webhook-secret",[384,385,11,11],"Get org","get-org",[387,388,11,11],"Request data deletion","request-data-deletion",[390,391,11,392],"Credits","credits",[393],[394,395,11,11],"Get credits","get-credits",[397,398,11,399],"Ledger","ledger",[400],[401,402,11,11],"List ledger entries","list-ledger-entries",[404,405,11,406],"Usage","usage",[407],[408,409,11,11],"Get usage","get-usage",[411,412,11,413],"API keys","api-keys",[414,417,420,423],[415,416,11,11],"List API keys","list-api-keys",[418,419,11,11],"Create API key","create-api-key",[421,422,11,11],"Rotate API key","rotate-api-key",[424,425,11,11],"Revoke API key","revoke-api-key",[427,108,11,428],"BYO providers",[429,432,435,438,441,444],[430,431,11,11],"List BYO providers","list-byo-providers",[433,434,11,11],"Create BYO provider","create-byo-provider",[436,437,11,11],"Get BYO provider","get-byo-provider",[439,440,11,11],"Update BYO provider","update-byo-provider",[442,443,11,11],"Delete BYO provider","delete-byo-provider",[445,446,11,11],"Test BYO provider","test-byo-provider",[448,449,11,450],"Sites","sites",[451],[452,453,11,11],"Get site profile","get-site-profile",[455,456,11,457],"Objects","schemas",[458,460,463,466,469,470,473,476,479,482,485,487,490,493,496,499,502,505,508,511,514,517],[27,459,11,11],"\u002Freference\u002Fapi\u002Fschemas",[461,462,11,11],"ApiKey","api-key",[464,465,11,11],"Batch","batch",[467,468,11,11],"ByoProvider","byo-provider",[390,391,11,11],[471,472,11,11],"ErrorResponse","error-response",[474,475,11,11],"ExtractSchema","extract-schema",[477,478,11,11],"Job","job",[480,481,11,11],"LedgerEntry","ledger-entry",[483,484,11,11],"LintResult","lint-result",[486,378,11,11],"Org",[488,489,11,11],"Program","program",[491,492,11,11],"ProgramDefinition","program-definition",[494,495,11,11],"ProgramVersion","program-version",[497,498,11,11],"Proposal","proposal",[500,501,11,11],"Schedule","schedule",[503,504,11,11],"Session","session",[506,507,11,11],"SiteProfile","site-profile",[509,510,11,11],"StepRecord","step-record",[512,513,11,11],"Webhook","webhook",[515,516,11,11],"WebhookDelivery","webhook-delivery",[518,519,11,11],"WebhookEvent","webhook-event",[521,522,11,523],"DSL","dsl",[524,526,529,532,535,538,603,637,640,751,754,756],[27,525,11,11],"\u002Freference\u002Fdsl",[527,528,11,11],"Inputs","inputs",[530,531,11,11],"Options","options",[533,534,11,11],"Policies","policies",[536,537,11,11],"Steps","steps",[539,540,11,541],"Actions","actions",[542,544,546,548,550,552,554,556,558,560,562,565,568,570,573,575,577,579,582,585,587,589,591,594,596,598,600],[27,543,11,11],"\u002Freference\u002Fdsl\u002Factions",[545,545,11,11],"fill",[547,547,11,11],"click",[549,549,11,11],"press",[551,551,11,11],"hover",[553,553,11,11],"select",[555,555,11,11],"check",[557,557,11,11],"uncheck",[559,559,11,11],"upload",[561,561,11,11],"scroll",[563,564,11,11],"wait_for","wait-for",[566,567,11,11],"set_session","set-session",[569,569,11,11],"extract",[571,572,11,11],"capture_response","capture-response",[574,574,11,11],"evaluate",[576,576,11,11],"paginate",[578,578,11,11],"download",[580,581,11,11],"read_pdf","read-pdf",[583,584,11,11],"set_vars","set-vars",[586,586,11,11],"sleep",[588,588,11,11],"reload",[590,590,11,11],"goto",[592,593,11,11],"await_input","await-input",[595,595,11,11],"checkpoint",[597,597,11,11],"screenshot",[599,599,11,11],"when",[601,602,11,11],"loop_until","loop-until",[604,605,11,606],"Conditions","conditions",[607,609,611,613,615,617,619,621,623,625,627,629,631,633,635],[27,608,11,11],"\u002Freference\u002Fdsl\u002Fconditions",[610,610,11,11],"url",[612,612,11,11],"title",[614,614,11,11],"visible",[616,616,11,11],"hidden",[618,618,11,11],"exists",[620,620,11,11],"absent",[622,622,11,11],"text",[624,624,11,11],"count",[626,626,11,11],"network",[628,628,11,11],"var",[630,630,11,11],"landmark",[632,632,11,11],"all",[634,634,11,11],"any",[636,636,11,11],"not",[638,639,11,11],"Targets","targets",[641,642,11,643],"Fields","fields",[644,646,649,651,654,656,659,661,663,666,668,671,672,674,677,679,681,683,685,687,690,693,695,698,701,703,704,707,709,712,714,716,718,720,722,724,726,728,731,733,734,736,738,740,743,745,747,749],[27,645,11,11],"\u002Freference\u002Fdsl\u002Ffields",[647,648,11,11],"allow_suffix","allow-suffix",[650,650,11,11],"attr",[652,653,11,11],"base_url","base-url",[655,655,11,11],"candidates",[657,658,11,11],"case_insensitive","case-insensitive",[660,660,11,11],"country",[662,662,11,11],"currency",[664,665,11,11],"date_format","date-format",[667,667,11,11],"decimal",[669,670,11,11],"false_values","false-values",[642,642,11,11],[673,673,11,11],"flags",[675,676,11,11],"follow_redirects","follow-redirects",[678,678,11,11],"frame",[680,680,11,11],"json",[682,682,11,11],"locale",[684,684,11,11],"many",[686,686,11,11],"max",[688,689,11,11],"max_items","max-items",[691,692,11,11],"max_length","max-length",[694,694,11,11],"min",[696,697,11,11],"min_items","min-items",[699,700,11,11],"min_length","min-length",[702,702,11,11],"name",[626,626,11,11],[705,706,11,11],"null_markers","null-markers",[708,708,11,11],"nullable",[710,711,11,11],"on_invalid","on-invalid",[713,713,11,11],"pattern",[715,715,11,11],"presence",[717,717,11,11],"regex",[719,719,11,11],"required",[721,721,11,11],"role",[723,723,11,11],"sample",[725,725,11,11],"selector",[727,727,11,11],"source",[729,730,11,11],"strip_fragment","strip-fragment",[732,732,11,11],"testid",[622,622,11,11],[735,735,11,11],"thousands",[737,737,11,11],"timezone",[739,739,11,11],"transforms",[741,742,11,11],"true_values","true-values",[744,744,11,11],"type",[746,746,11,11],"unique",[748,748,11,11],"values",[750,750,11,11],"xpath",[752,753,11,11],"Assertions","assertions",[755,739,11,11],"Transforms",[757,758,11,759],"Lint codes","lint",[760,762,765,768,771,774,777,780,783,786,789,792,795,798,801,804,807,810,813,816,819,822,825,828,831],[27,761,11,11],"\u002Freference\u002Fdsl\u002Flint",[763,764,11,11],"E001","e001",[766,767,11,11],"E002","e002",[769,770,11,11],"E003","e003",[772,773,11,11],"E004","e004",[775,776,11,11],"E005","e005",[778,779,11,11],"E006","e006",[781,782,11,11],"E007","e007",[784,785,11,11],"E008","e008",[787,788,11,11],"E009","e009",[790,791,11,11],"E010","e010",[793,794,11,11],"E011","e011",[796,797,11,11],"E012","e012",[799,800,11,11],"E013","e013",[802,803,11,11],"E014","e014",[805,806,11,11],"W001","w001",[808,809,11,11],"W002","w002",[811,812,11,11],"W003","w003",[814,815,11,11],"W004","w004",[817,818,11,11],"W005","w005",[820,821,11,11],"W006","w006",[823,824,11,11],"W007","w007",[826,827,11,11],"W008","w008",[829,830,11,11],"W009","w009",[832,833,11,11],"W010","w010",[835,836,11,11],"Codes","codes",[838,839,11,11],"Events","events",[841,129,11,842],"CLI",[843,845,847,850,853,856,859,862,865,868,871,874,877,880,883,886,889,892,895,898,901,904,907,910,913,916,919,922,925,928,931,934,937,940,943,946,949,952,955,958,961,964,967,970,973,976,979,982,985,988,991,994,997,1000,1003],[27,844,11,11],"\u002Freference\u002Fcli",[846,846,11,11],"kaab",[848,849,11,11],"kaab auth","kaab-auth",[851,852,11,11],"kaab auth login","kaab-auth-login",[854,855,11,11],"kaab auth logout","kaab-auth-logout",[857,858,11,11],"kaab auth status","kaab-auth-status",[860,861,11,11],"kaab completion","kaab-completion",[863,864,11,11],"kaab completion bash","kaab-completion-bash",[866,867,11,11],"kaab completion fish","kaab-completion-fish",[869,870,11,11],"kaab completion powershell","kaab-completion-powershell",[872,873,11,11],"kaab completion zsh","kaab-completion-zsh",[875,876,11,11],"kaab credits","kaab-credits",[878,879,11,11],"kaab jobs","kaab-jobs",[881,882,11,11],"kaab jobs cancel","kaab-jobs-cancel",[884,885,11,11],"kaab jobs get","kaab-jobs-get",[887,888,11,11],"kaab jobs input","kaab-jobs-input",[890,891,11,11],"kaab jobs list","kaab-jobs-list",[893,894,11,11],"kaab jobs resume","kaab-jobs-resume",[896,897,11,11],"kaab jobs run","kaab-jobs-run",[899,900,11,11],"kaab jobs snapshot","kaab-jobs-snapshot",[902,903,11,11],"kaab jobs steps","kaab-jobs-steps",[905,906,11,11],"kaab jobs takeover","kaab-jobs-takeover",[908,909,11,11],"kaab open","kaab-open",[911,912,11,11],"kaab open job","kaab-open-job",[914,915,11,11],"kaab programs","kaab-programs",[917,918,11,11],"kaab programs alias","kaab-programs-alias",[920,921,11,11],"kaab programs alias set","kaab-programs-alias-set",[923,924,11,11],"kaab programs diff","kaab-programs-diff",[926,927,11,11],"kaab programs lint","kaab-programs-lint",[929,930,11,11],"kaab programs list","kaab-programs-list",[932,933,11,11],"kaab programs pull","kaab-programs-pull",[935,936,11,11],"kaab programs push","kaab-programs-push",[938,939,11,11],"kaab proposals","kaab-proposals",[941,942,11,11],"kaab proposals approve","kaab-proposals-approve",[944,945,11,11],"kaab proposals list","kaab-proposals-list",[947,948,11,11],"kaab proposals reject","kaab-proposals-reject",[950,951,11,11],"kaab proposals show","kaab-proposals-show",[953,954,11,11],"kaab schedules","kaab-schedules",[956,957,11,11],"kaab schedules create","kaab-schedules-create",[959,960,11,11],"kaab schedules list","kaab-schedules-list",[962,963,11,11],"kaab schedules pause","kaab-schedules-pause",[965,966,11,11],"kaab schedules resume","kaab-schedules-resume",[968,969,11,11],"kaab schedules runs","kaab-schedules-runs",[971,972,11,11],"kaab sessions","kaab-sessions",[974,975,11,11],"kaab sessions create","kaab-sessions-create",[977,978,11,11],"kaab sessions delete","kaab-sessions-delete",[980,981,11,11],"kaab sessions list","kaab-sessions-list",[983,984,11,11],"kaab sessions verify","kaab-sessions-verify",[986,987,11,11],"kaab usage","kaab-usage",[989,990,11,11],"kaab version","kaab-version",[992,993,11,11],"kaab webhooks","kaab-webhooks",[995,996,11,11],"kaab webhooks create","kaab-webhooks-create",[998,999,11,11],"kaab webhooks deliveries","kaab-webhooks-deliveries",[1001,1002,11,11],"kaab webhooks list","kaab-webhooks-list",[1004,1005,11,11],"kaab webhooks test","kaab-webhooks-test",[125,126,11,11],[1008,1009,11,11],"Plans and pricing","plans",[1011,1012,1013,1014],"Changelog","\u002Fchangelog","i-lucide-history",[1015,1016],[27,1012,11,11],[1017,1018,11,11],"Deprecations","deprecations",{"id":1020,"title":104,"body":1021,"breaking":5834,"date":5835,"description":5836,"extension":5837,"generated":5834,"meta":5838,"navigation":1802,"path":5839,"seo":5840,"stem":5841,"version":5835,"__hash__":5842},"docs\u002F3.guides\u002F06.webhooks.md",{"type":1022,"value":1023,"toc":5814},"minimark",[1024,1028,1033,1036,1052,1059,1063,1073,1457,1472,1594,1612,1620,1648,1652,1662,1712,1719,1723,1905,1980,1982,2525,2544,2548,2572,2575,2591,2594,3666,3670,3684,5354,5358,5376,5400,5418,5430,5462,5466,5506,5510,5515,5535,5539,5576,5580,5583,5702,5706,5751,5754,5810],[1025,1026,1027],"p",{},"Kaabist sends an HTTP request to your server when something you care about happens: a job finished,\na job needs a person, a program started drifting, credits are running low. Every delivery is signed,\nso you can prove it came from Kaabist and was not changed on the way, and your systems can react\nwithout polling.",[1029,1030,1032],"h2",{"id":1031},"when-to-use-it","When to use it",[1025,1034,1035],{},"Use webhooks when your system should react to Kaabist rather than ask it: start processing when a\njob succeeds, page someone when one needs attention, act on a schedule's change, top up credits\nwhen they run low.",[1025,1037,1038,1039,1043,1044,1047,1048,1051],{},"Polling is the simpler choice when one script creates a job and waits for it: ",[1040,1041,1042],"code",{},"Prefer: wait"," on\n",[1040,1045,1046],{},"POST \u002Fv1\u002Fjobs",", or an SDK's ",[1040,1049,1050],{},"jobs.wait",", needs no public endpoint at all. Both work together; a\nwebhook is a notification, and the API stays the source of truth.",[1025,1053,1054,1055,1058],{},"A webhook is a notification, not a copy of your data. The body names what happened and where to\nread it (",[1040,1056,1057],{},"data.url","); fetch the job, proposal or program from the API when you need the details.\nJob results, inputs, sessions and credentials are never in a webhook.",[1029,1060,1062],{"id":1061},"registering-an-endpoint","Registering an endpoint",[1025,1064,1065,1068,1069,1072],{},[1040,1066,1067],{},"POST \u002Fv1\u002Fwebhooks"," with an ",[1040,1070,1071],{},"admin"," key registers a URL and the events it wants:",[1074,1075,1076,1172,1240],"code-group",{},[1077,1078,1084],"pre",{"className":1079,"code":1080,"filename":1081,"language":1082,"meta":1083,"style":1083},"language-ts shiki shiki-themes kaab-code kaab-code kaab-code","const endpoint = await kaab.webhooks.create({ url, events: [\"job.*\"], description: \"example\" });\nsecret = endpoint.secret; \u002F\u002F shown once: store it where the receiver can read it\n","TypeScript","ts","",[1040,1085,1086,1154],{"__ignoreMap":1083},[1087,1088,1091,1095,1099,1102,1105,1108,1112,1114,1117,1121,1125,1128,1131,1134,1138,1141,1143,1146,1148,1151],"span",{"class":1089,"line":1090},"line",1,[1087,1092,1094],{"class":1093},"sqn18","const",[1087,1096,1098],{"class":1097},"sHJIl"," endpoint",[1087,1100,1101],{"class":1093}," =",[1087,1103,1104],{"class":1093}," await",[1087,1106,1107],{"class":1097}," kaab",[1087,1109,1111],{"class":1110},"sJugJ",".",[1087,1113,105],{"class":1097},[1087,1115,1116],{"class":1110},".create",[1087,1118,1120],{"class":1119},"sTzsn","({ url",[1087,1122,1124],{"class":1123},"sQm2-",",",[1087,1126,1127],{"class":1119}," events",[1087,1129,1130],{"class":1093},":",[1087,1132,1133],{"class":1119}," [",[1087,1135,1137],{"class":1136},"suOy4","\"job.*\"",[1087,1139,1140],{"class":1119},"]",[1087,1142,1124],{"class":1123},[1087,1144,1145],{"class":1119}," description",[1087,1147,1130],{"class":1093},[1087,1149,1150],{"class":1136}," \"example\"",[1087,1152,1153],{"class":1119}," });\n",[1087,1155,1157,1160,1163,1165,1168],{"class":1089,"line":1156},2,[1087,1158,1159],{"class":1119},"secret ",[1087,1161,1162],{"class":1093},"=",[1087,1164,1098],{"class":1097},[1087,1166,1167],{"class":1119},".secret; ",[1087,1169,1171],{"class":1170},"sKog0","\u002F\u002F shown once: store it where the receiver can read it\n",[1077,1173,1178],{"className":1174,"code":1175,"filename":1176,"language":1177,"meta":1083,"style":1083},"language-python shiki shiki-themes kaab-code kaab-code kaab-code","endpoint = kaab.webhooks.create(url=url, events=[\"job.*\"], description=\"example\")\nsecret = endpoint.secret  # shown once: store it where the receiver can read it\n","Python","python",[1040,1179,1180,1224],{"__ignoreMap":1083},[1087,1181,1182,1185,1187,1189,1191,1193,1195,1198,1201,1203,1206,1208,1211,1213,1216,1218,1221],{"class":1089,"line":1090},[1087,1183,1184],{"class":1119},"endpoint ",[1087,1186,1162],{"class":1093},[1087,1188,1107],{"class":1119},[1087,1190,1111],{"class":1123},[1087,1192,105],{"class":1119},[1087,1194,1111],{"class":1123},[1087,1196,1197],{"class":1110},"create",[1087,1199,1200],{"class":1123},"(url",[1087,1202,1162],{"class":1093},[1087,1204,1205],{"class":1123},"url, events",[1087,1207,1162],{"class":1093},[1087,1209,1210],{"class":1123},"[",[1087,1212,1137],{"class":1136},[1087,1214,1215],{"class":1123},"], description",[1087,1217,1162],{"class":1093},[1087,1219,1220],{"class":1136},"\"example\"",[1087,1222,1223],{"class":1123},")\n",[1087,1225,1226,1228,1230,1232,1234,1237],{"class":1089,"line":1156},[1087,1227,1159],{"class":1119},[1087,1229,1162],{"class":1093},[1087,1231,1098],{"class":1119},[1087,1233,1111],{"class":1123},[1087,1235,1236],{"class":1119},"secret  ",[1087,1238,1239],{"class":1170},"# shown once: store it where the receiver can read it\n",[1077,1241,1246],{"className":1242,"code":1243,"filename":1244,"language":1245,"meta":1083,"style":1083},"language-go shiki shiki-themes kaab-code kaab-code kaab-code","body, err := kaab.Body[client.CreateWebhookJSONBody](map[string]any{\"url\": url, \"events\": []string{\"job.*\"}, \"description\": \"example\"})\nif err != nil {\n    log.Fatal(err)\n}\nendpoint, err := c.Webhooks.Create(ctx, body)\nif err != nil {\n    log.Fatal(err)\n}\ndefer func() { _ = c.Webhooks.Delete(ctx, endpoint.ID) }()\nmu.Lock()\nsecret = endpoint.Secret \u002F\u002F shown once: store it where the receiver can read it\nmu.Unlock()\n","Go","go",[1040,1247,1248,1322,1339,1351,1357,1374,1387,1396,1401,1423,1435,1447],{"__ignoreMap":1083},[1087,1249,1250,1253,1256,1259,1262,1264,1267,1269,1272,1275,1278,1280,1283,1285,1287,1290,1293,1296,1299,1302,1304,1306,1308,1311,1314,1317,1319],{"class":1089,"line":1090},[1087,1251,1252],{"class":1119},"body, err ",[1087,1254,1255],{"class":1093},":=",[1087,1257,1258],{"class":1119}," kaab.",[1087,1260,1261],{"class":1110},"Body",[1087,1263,1210],{"class":1119},[1087,1265,1266],{"class":1110},"client",[1087,1268,1111],{"class":1119},[1087,1270,1271],{"class":1110},"CreateWebhookJSONBody",[1087,1273,1274],{"class":1119},"](",[1087,1276,1277],{"class":1093},"map",[1087,1279,1210],{"class":1119},[1087,1281,1282],{"class":1093},"string",[1087,1284,1140],{"class":1119},[1087,1286,634],{"class":1110},[1087,1288,1289],{"class":1119},"{",[1087,1291,1292],{"class":1136},"\"url\"",[1087,1294,1295],{"class":1119},": url, ",[1087,1297,1298],{"class":1136},"\"events\"",[1087,1300,1301],{"class":1119},": []",[1087,1303,1282],{"class":1093},[1087,1305,1289],{"class":1119},[1087,1307,1137],{"class":1136},[1087,1309,1310],{"class":1119},"}, ",[1087,1312,1313],{"class":1136},"\"description\"",[1087,1315,1316],{"class":1119},": ",[1087,1318,1220],{"class":1136},[1087,1320,1321],{"class":1119},"})\n",[1087,1323,1324,1327,1330,1333,1336],{"class":1089,"line":1156},[1087,1325,1326],{"class":1093},"if",[1087,1328,1329],{"class":1119}," err ",[1087,1331,1332],{"class":1093},"!=",[1087,1334,1335],{"class":1097}," nil",[1087,1337,1338],{"class":1119}," {\n",[1087,1340,1342,1345,1348],{"class":1089,"line":1341},3,[1087,1343,1344],{"class":1119},"    log.",[1087,1346,1347],{"class":1110},"Fatal",[1087,1349,1350],{"class":1119},"(err)\n",[1087,1352,1354],{"class":1089,"line":1353},4,[1087,1355,1356],{"class":1119},"}\n",[1087,1358,1360,1363,1365,1368,1371],{"class":1089,"line":1359},5,[1087,1361,1362],{"class":1119},"endpoint, err ",[1087,1364,1255],{"class":1093},[1087,1366,1367],{"class":1119}," c.Webhooks.",[1087,1369,1370],{"class":1110},"Create",[1087,1372,1373],{"class":1119},"(ctx, body)\n",[1087,1375,1377,1379,1381,1383,1385],{"class":1089,"line":1376},6,[1087,1378,1326],{"class":1093},[1087,1380,1329],{"class":1119},[1087,1382,1332],{"class":1093},[1087,1384,1335],{"class":1097},[1087,1386,1338],{"class":1119},[1087,1388,1390,1392,1394],{"class":1089,"line":1389},7,[1087,1391,1344],{"class":1119},[1087,1393,1347],{"class":1110},[1087,1395,1350],{"class":1119},[1087,1397,1399],{"class":1089,"line":1398},8,[1087,1400,1356],{"class":1119},[1087,1402,1404,1407,1410,1413,1415,1417,1420],{"class":1089,"line":1403},9,[1087,1405,1406],{"class":1093},"defer",[1087,1408,1409],{"class":1093}," func",[1087,1411,1412],{"class":1119},"() { _ ",[1087,1414,1162],{"class":1093},[1087,1416,1367],{"class":1119},[1087,1418,1419],{"class":1110},"Delete",[1087,1421,1422],{"class":1119},"(ctx, endpoint.ID) }()\n",[1087,1424,1426,1429,1432],{"class":1089,"line":1425},10,[1087,1427,1428],{"class":1119},"mu.",[1087,1430,1431],{"class":1110},"Lock",[1087,1433,1434],{"class":1119},"()\n",[1087,1436,1438,1440,1442,1445],{"class":1089,"line":1437},11,[1087,1439,1159],{"class":1119},[1087,1441,1162],{"class":1093},[1087,1443,1444],{"class":1119}," endpoint.Secret ",[1087,1446,1171],{"class":1170},[1087,1448,1450,1452,1455],{"class":1089,"line":1449},12,[1087,1451,1428],{"class":1119},[1087,1453,1454],{"class":1110},"Unlock",[1087,1456,1434],{"class":1119},[1025,1458,1459,1460,1316,1463,1466,1467,1471],{},"The response carries ",[1040,1461,1462],{},"secret",[1040,1464,1465],{},"whsec_"," followed by 43 characters. ",[1468,1469,1470],"strong",{},"It is shown once."," Store it\nwhere your receiver can read it; Kaabist keeps only an encrypted copy.",[1473,1474,1475,1488],"table",{},[1476,1477,1478],"thead",{},[1479,1480,1481,1485],"tr",{},[1482,1483,1484],"th",{},"Field",[1482,1486,1487],{},"Meaning",[1489,1490,1491,1510,1535,1564,1584],"tbody",{},[1479,1492,1493,1498],{},[1494,1495,1496],"td",{},[1040,1497,610],{},[1494,1499,1500,1503,1504,1509],{},[1040,1501,1502],{},"https:\u002F\u002F"," only. It must resolve to a public address, every time it is called (see ",[1505,1506,1508],"a",{"href":1507},"\u002Fguides\u002Fwebhooks#where-deliveries-may-go","where deliveries may go",").",[1479,1511,1512,1516],{},[1494,1513,1514],{},[1040,1515,839],{},[1494,1517,1518,1519,1522,1523,1526,1527,1530,1531,1534],{},"Patterns: an exact type (",[1040,1520,1521],{},"job.succeeded","), a group (",[1040,1524,1525],{},"job.*",", every type starting ",[1040,1528,1529],{},"job.","), or ",[1040,1532,1533],{},"*"," for everything. Unknown types are refused when the endpoint is saved.",[1479,1536,1537,1542],{},[1494,1538,1539],{},[1040,1540,1541],{},"mode",[1494,1543,1544,1547,1548,1551,1552,1555,1556,1559,1560,1563],{},[1040,1545,1546],{},"live",", ",[1040,1549,1550],{},"test"," or ",[1040,1553,1554],{},"both"," (the default): which keys' events the endpoint receives. A job created with an ",[1040,1557,1558],{},"sk_test_"," key produces ",[1040,1561,1562],{},"mode: \"test\""," events.",[1479,1565,1566,1571],{},[1494,1567,1568],{},[1040,1569,1570],{},"format",[1494,1572,1573,1576,1577,1551,1580,1583],{},[1040,1574,1575],{},"standard"," (the default): the signed event body described below. ",[1040,1578,1579],{},"discord",[1040,1581,1582],{},"slack",": each event arrives as a chat message instead.",[1479,1585,1586,1591],{},[1494,1587,1588],{},[1040,1589,1590],{},"description",[1494,1592,1593],{},"Free text for your own bookkeeping.",[1025,1595,1596,1599,1600,1603,1604,1607,1608,1611],{},[1040,1597,1598],{},"PATCH \u002Fv1\u002Fwebhooks\u002F{id}"," changes any of them, ",[1040,1601,1602],{},"DELETE"," removes the endpoint, and\n",[1040,1605,1606],{},"POST \u002Fv1\u002Fwebhooks\u002F{id}\u002Ftest"," sends a ",[1040,1609,1610],{},"ping"," event and waits up to ten seconds for your answer,\nwhich it returns as a delivery record.",[1613,1614,1616,1617],"h3",{"id":1615},"a-jobs-own-webhook_url","A job's own ",[1040,1618,1619],{},"webhook_url",[1025,1621,1622,1623,1625,1626,1628,1629,1631,1632,1635,1636,1639,1640,1643,1644,1647],{},"A job can also name a one-off destination: ",[1040,1624,1619],{}," on ",[1040,1627,1046],{}," (or on a batch)\nreceives that job's ",[1040,1630,1525],{}," events (or the batch's ",[1040,1633,1634],{},"batch.completed",") whether or not an endpoint\nmatches. Those deliveries are always in the standard format and signed with your\n",[1468,1637,1638],{},"organisation's"," signing secret; get it with ",[1040,1641,1642],{},"POST \u002Fv1\u002Forg\u002Fwebhook-secret\u002Frotate"," (send ",[1040,1645,1646],{},"{}","),\nwhich, like an endpoint rotation, keeps the previous secret valid for a day.",[1613,1649,1651],{"id":1650},"discord-and-slack","Discord and Slack",[1025,1653,1654,1655,1658,1659,1661],{},"Discord's and Slack's incoming webhooks accept only their own message shape, and answer the\nstandard event body with a ",[1040,1656,1657],{},"400",". Give such an endpoint the matching ",[1040,1660,1570],{}," and every event\narrives as a message instead:",[1077,1663,1666],{"className":1664,"code":1665,"language":680,"meta":1083,"style":1083},"language-json shiki shiki-themes kaab-code kaab-code kaab-code","{ \"url\": \"https:\u002F\u002Fdiscord.com\u002Fapi\u002Fwebhooks\u002F…\", \"events\": [\"job.failed\", \"schedule.changed\"], \"format\": \"discord\" }\n",[1040,1667,1668],{"__ignoreMap":1083},[1087,1669,1670,1673,1675,1677,1680,1682,1685,1687,1689,1692,1694,1697,1699,1701,1704,1706,1709],{"class":1089,"line":1090},[1087,1671,1672],{"class":1119},"{ ",[1087,1674,1292],{"class":1093},[1087,1676,1130],{"class":1123},[1087,1678,1679],{"class":1136}," \"https:\u002F\u002Fdiscord.com\u002Fapi\u002Fwebhooks\u002F…\"",[1087,1681,1124],{"class":1123},[1087,1683,1684],{"class":1093}," \"events\"",[1087,1686,1130],{"class":1123},[1087,1688,1133],{"class":1119},[1087,1690,1691],{"class":1136},"\"job.failed\"",[1087,1693,1124],{"class":1123},[1087,1695,1696],{"class":1136}," \"schedule.changed\"",[1087,1698,1140],{"class":1119},[1087,1700,1124],{"class":1123},[1087,1702,1703],{"class":1093}," \"format\"",[1087,1705,1130],{"class":1123},[1087,1707,1708],{"class":1136}," \"discord\"",[1087,1710,1711],{"class":1119}," }\n",[1025,1713,1714,1715,1718],{},"The message says what happened (\"Job failed\", \"Scheduled run found a change\"), lists the event's\n",[1040,1716,1717],{},"data"," (the job, the program, the reason, the changed paths, …), and links to the job, schedule or\nprogram in the panel. It carries what the event carries and nothing more: a job's result is never in\na webhook, so a price change shows as the changed path with a link to read the new value. Discord\nmessages mention nobody, whatever a program is called. Deliveries are still signed and retried like\nany other. The panel picks the format by itself when you paste a Discord or Slack address.",[1029,1720,1722],{"id":1721},"what-a-delivery-looks-like","What a delivery looks like",[1077,1724,1728],{"className":1725,"code":1726,"language":1727,"meta":1083,"style":1083},"language-http shiki shiki-themes kaab-code kaab-code kaab-code","POST \u002Fkaab HTTP\u002F1.1\nContent-Type: application\u002Fjson\nUser-Agent: Kaab-Webhooks\u002F1\nKaab-Event-Id: evt_01JB8Q3Y2N6T4W9H5K7M1P0R3S\nKaab-Event-Type: job.needs_attention\nKaab-Signature: t=1758362400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd\n\n{\"id\":\"evt_01JB8Q3Y2N6T4W9H5K7M1P0R3S\",\"type\":\"job.needs_attention\",\"created_at\":\"2026-09-20T10:00:00.000Z\",\"org\":\"org_01JB8Q2V…\",\"mode\":\"live\",\"data\":{\"job\":\"job_01JB8Q3X…\",\"program\":\"acme-report@3\",\"reason\":\"obstacle\",\"url\":\"https:\u002F\u002Fapi.kaab.ist\u002Fv1\u002Fjobs\u002Fjob_01JB8Q3X…\"}}\n","http",[1040,1729,1730,1747,1758,1768,1778,1788,1798,1804],{"__ignoreMap":1083},[1087,1731,1732,1735,1738,1741,1744],{"class":1089,"line":1090},[1087,1733,1734],{"class":1093},"POST",[1087,1736,1737],{"class":1119}," \u002Fkaab ",[1087,1739,1740],{"class":1093},"HTTP",[1087,1742,1743],{"class":1119},"\u002F",[1087,1745,1746],{"class":1097},"1.1\n",[1087,1748,1749,1752,1754],{"class":1089,"line":1156},[1087,1750,1751],{"class":1136},"Content-Type",[1087,1753,1130],{"class":1093},[1087,1755,1757],{"class":1756},"sx8Bj"," application\u002Fjson\n",[1087,1759,1760,1763,1765],{"class":1089,"line":1341},[1087,1761,1762],{"class":1136},"User-Agent",[1087,1764,1130],{"class":1093},[1087,1766,1767],{"class":1756}," Kaab-Webhooks\u002F1\n",[1087,1769,1770,1773,1775],{"class":1089,"line":1353},[1087,1771,1772],{"class":1136},"Kaab-Event-Id",[1087,1774,1130],{"class":1093},[1087,1776,1777],{"class":1756}," evt_01JB8Q3Y2N6T4W9H5K7M1P0R3S\n",[1087,1779,1780,1783,1785],{"class":1089,"line":1359},[1087,1781,1782],{"class":1136},"Kaab-Event-Type",[1087,1784,1130],{"class":1093},[1087,1786,1787],{"class":1756}," job.needs_attention\n",[1087,1789,1790,1793,1795],{"class":1089,"line":1376},[1087,1791,1792],{"class":1136},"Kaab-Signature",[1087,1794,1130],{"class":1093},[1087,1796,1797],{"class":1756}," t=1758362400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd\n",[1087,1799,1800],{"class":1089,"line":1389},[1087,1801,1803],{"emptyLinePlaceholder":1802},true,"\n",[1087,1805,1806,1808,1811,1813,1816,1818,1821,1823,1826,1828,1831,1833,1836,1838,1841,1843,1846,1848,1851,1853,1856,1858,1861,1863,1865,1868,1870,1873,1875,1878,1880,1883,1885,1888,1890,1893,1895,1897,1899,1902],{"class":1089,"line":1398},[1087,1807,1289],{"class":1119},[1087,1809,1810],{"class":1093},"\"id\"",[1087,1812,1130],{"class":1123},[1087,1814,1815],{"class":1136},"\"evt_01JB8Q3Y2N6T4W9H5K7M1P0R3S\"",[1087,1817,1124],{"class":1123},[1087,1819,1820],{"class":1093},"\"type\"",[1087,1822,1130],{"class":1123},[1087,1824,1825],{"class":1136},"\"job.needs_attention\"",[1087,1827,1124],{"class":1123},[1087,1829,1830],{"class":1093},"\"created_at\"",[1087,1832,1130],{"class":1123},[1087,1834,1835],{"class":1136},"\"2026-09-20T10:00:00.000Z\"",[1087,1837,1124],{"class":1123},[1087,1839,1840],{"class":1093},"\"org\"",[1087,1842,1130],{"class":1123},[1087,1844,1845],{"class":1136},"\"org_01JB8Q2V…\"",[1087,1847,1124],{"class":1123},[1087,1849,1850],{"class":1093},"\"mode\"",[1087,1852,1130],{"class":1123},[1087,1854,1855],{"class":1136},"\"live\"",[1087,1857,1124],{"class":1123},[1087,1859,1860],{"class":1093},"\"data\"",[1087,1862,1130],{"class":1123},[1087,1864,1289],{"class":1119},[1087,1866,1867],{"class":1093},"\"job\"",[1087,1869,1130],{"class":1123},[1087,1871,1872],{"class":1136},"\"job_01JB8Q3X…\"",[1087,1874,1124],{"class":1123},[1087,1876,1877],{"class":1093},"\"program\"",[1087,1879,1130],{"class":1123},[1087,1881,1882],{"class":1136},"\"acme-report@3\"",[1087,1884,1124],{"class":1123},[1087,1886,1887],{"class":1093},"\"reason\"",[1087,1889,1130],{"class":1123},[1087,1891,1892],{"class":1136},"\"obstacle\"",[1087,1894,1124],{"class":1123},[1087,1896,1292],{"class":1093},[1087,1898,1130],{"class":1123},[1087,1900,1901],{"class":1136},"\"https:\u002F\u002Fapi.kaab.ist\u002Fv1\u002Fjobs\u002Fjob_01JB8Q3X…\"",[1087,1903,1904],{"class":1119},"}}\n",[1473,1906,1907,1916],{},[1476,1908,1909],{},[1479,1910,1911,1914],{},[1482,1912,1913],{},"Key",[1482,1915,1487],{},[1489,1917,1918,1928,1937,1947,1956,1968],{},[1479,1919,1920,1925],{},[1494,1921,1922],{},[1040,1923,1924],{},"id",[1494,1926,1927],{},"The event's id. The same event delivered twice has the same id; use it to deduplicate.",[1479,1929,1930,1934],{},[1494,1931,1932],{},[1040,1933,744],{},[1494,1935,1936],{},"One of the types below.",[1479,1938,1939,1944],{},[1494,1940,1941],{},[1040,1942,1943],{},"created_at",[1494,1945,1946],{},"When the event happened (not when it was delivered).",[1479,1948,1949,1953],{},[1494,1950,1951],{},[1040,1952,378],{},[1494,1954,1955],{},"Your organisation.",[1479,1957,1958,1962],{},[1494,1959,1960],{},[1040,1961,1541],{},[1494,1963,1964,1551,1966,1111],{},[1040,1965,1546],{},[1040,1967,1550],{},[1479,1969,1970,1974],{},[1494,1971,1972],{},[1040,1973,1717],{},[1494,1975,1976,1977,1979],{},"The event's fields, always including ",[1040,1978,610],{},": the API resource the event is about.",[1029,1981,838],{"id":839},[1473,1983,1984,1998],{},[1476,1985,1986],{},[1479,1987,1988,1991,1994],{},[1482,1989,1990],{},"Type",[1482,1992,1993],{},"When",[1482,1995,1996],{},[1040,1997,1717],{},[1489,1999,2000,2018,2044,2062,2086,2107,2124,2145,2168,2186,2209,2234,2265,2292,2315,2336,2354,2389,2409,2425,2441,2464,2484,2510],{},[1479,2001,2002,2007,2010],{},[1494,2003,2004],{},[1040,2005,2006],{},"job.queued",[1494,2008,2009],{},"A job was accepted, re-queued after a lease was lost, resumed, or escalated to a higher tier.",[1494,2011,2012,1547,2014,1547,2016],{},[1040,2013,478],{},[1040,2015,489],{},[1040,2017,610],{},[1479,2019,2020,2025,2028],{},[1494,2021,2022],{},[1040,2023,2024],{},"job.blocked",[1494,2026,2027],{},"A queued job is being held back: the organisation is out of credits. Sent once per blocked stretch.",[1494,2029,2030,1547,2032,1547,2034,2037,2038,2041,2042],{},[1040,2031,478],{},[1040,2033,489],{},[1040,2035,2036],{},"reason"," (",[1040,2039,2040],{},"insufficient_credits","), ",[1040,2043,610],{},[1479,2045,2046,2051,2054],{},[1494,2047,2048],{},[1040,2049,2050],{},"job.started",[1494,2052,2053],{},"A worker began running the job (its first run only).",[1494,2055,2056,1547,2058,1547,2060],{},[1040,2057,478],{},[1040,2059,489],{},[1040,2061,610],{},[1479,2063,2064,2069,2078],{},[1494,2065,2066],{},[1040,2067,2068],{},"job.awaiting_input",[1494,2070,2071,2072,2074,2075,1509],{},"The job parked at an ",[1040,2073,592],{}," step and needs a value (",[1040,2076,2077],{},"POST \u002Fjobs\u002F{id}\u002Finput",[1494,2079,2080,1547,2082,1547,2084],{},[1040,2081,478],{},[1040,2083,489],{},[1040,2085,610],{},[1479,2087,2088,2093,2096],{},[1494,2089,2090],{},[1040,2091,2092],{},"job.needs_attention",[1494,2094,2095],{},"The job parked and needs a decision: an obstacle, drift, an unknown page, an expired session.",[1494,2097,2098,1547,2100,1547,2102,2104,2105],{},[1040,2099,478],{},[1040,2101,489],{},[1040,2103,2036],{}," (the attention reason), ",[1040,2106,610],{},[1479,2108,2109,2113,2116],{},[1494,2110,2111],{},[1040,2112,1521],{},[1494,2114,2115],{},"The job finished and its result can be read.",[1494,2117,2118,1547,2120,1547,2122],{},[1040,2119,478],{},[1040,2121,489],{},[1040,2123,610],{},[1479,2125,2126,2131,2134],{},[1494,2127,2128],{},[1040,2129,2130],{},"job.failed",[1494,2132,2133],{},"The job ended without a result.",[1494,2135,2136,1547,2138,1547,2140,2142,2143],{},[1040,2137,478],{},[1040,2139,489],{},[1040,2141,2036],{}," (the error code), ",[1040,2144,610],{},[1479,2146,2147,2152,2155],{},[1494,2148,2149],{},[1040,2150,2151],{},"job.canceled",[1494,2153,2154],{},"The job was canceled or aborted.",[1494,2156,2157,1547,2159,1547,2161,2037,2163,2041,2166],{},[1040,2158,478],{},[1040,2160,489],{},[1040,2162,2036],{},[1040,2164,2165],{},"canceled",[1040,2167,610],{},[1479,2169,2170,2174,2177],{},[1494,2171,2172],{},[1040,2173,1634],{},[1494,2175,2176],{},"Every job of a batch has finished.",[1494,2178,2179,1547,2181,1547,2184],{},[1040,2180,465],{},[1040,2182,2183],{},"total",[1040,2185,610],{},[1479,2187,2188,2193,2196],{},[1494,2189,2190],{},[1040,2191,2192],{},"proposal.created",[1494,2194,2195],{},"Kaabist proposed a fix (a relocated locator, a dismiss recipe) for review.",[1494,2197,2198,1547,2200,1547,2202,1547,2204,1547,2207],{},[1040,2199,498],{},[1040,2201,489],{},[1040,2203,478],{},[1040,2205,2206],{},"kind",[1040,2208,610],{},[1479,2210,2211,2216,2219],{},[1494,2212,2213],{},[1040,2214,2215],{},"proposal.decided",[1494,2217,2218],{},"Somebody approved or rejected a proposal.",[1494,2220,2221,1547,2223,1547,2225,1547,2227,1547,2229,1547,2232],{},[1040,2222,498],{},[1040,2224,489],{},[1040,2226,478],{},[1040,2228,2206],{},[1040,2230,2231],{},"decision",[1040,2233,610],{},[1479,2235,2236,2241,2251],{},[1494,2237,2238],{},[1040,2239,2240],{},"schedule.run",[1494,2242,2243,2244,2247,2248,1509],{},"A scheduled job ended with nothing to report: the result is the same as last time (",[1040,2245,2246],{},"changed: false","), or the job did not succeed (",[1040,2249,2250],{},"status",[1494,2252,2253,1547,2255,1547,2257,1547,2259,2262,2263],{},[1040,2254,501],{},[1040,2256,478],{},[1040,2258,2250],{},[1040,2260,2261],{},"changed","?, ",[1040,2264,610],{},[1479,2266,2267,2272,2275],{},[1494,2268,2269],{},[1040,2270,2271],{},"schedule.changed",[1494,2273,2274],{},"A scheduled run's watched fields differ from the previous run's.",[1494,2276,2277,1547,2279,1547,2281,1547,2284,2262,2287,1547,2289],{},[1040,2278,501],{},[1040,2280,478],{},[1040,2282,2283],{},"changed_paths",[1040,2285,2286],{},"changed_paths_truncated",[1040,2288,610],{},[1040,2290,2291],{},"diff_url",[1479,2293,2294,2299,2302],{},[1494,2295,2296],{},[1040,2297,2298],{},"schedule.paused",[1494,2300,2301],{},"A schedule was paused: out of credits, or by hand.",[1494,2303,2304,1547,2306,2037,2308,1551,2310,2041,2313],{},[1040,2305,501],{},[1040,2307,2036],{},[1040,2309,391],{},[1040,2311,2312],{},"manual",[1040,2314,610],{},[1479,2316,2317,2322,2325],{},[1494,2318,2319],{},[1040,2320,2321],{},"schedule.failing",[1494,2323,2324],{},"Five runs of a schedule in a row did not succeed; it keeps running.",[1494,2326,2327,1547,2329,1547,2331,1547,2334],{},[1040,2328,501],{},[1040,2330,478],{},[1040,2332,2333],{},"failure_streak",[1040,2335,610],{},[1479,2337,2338,2343,2346],{},[1494,2339,2340],{},[1040,2341,2342],{},"session.expired",[1494,2344,2345],{},"A stored session stopped working.",[1494,2347,2348,1547,2350,1547,2352],{},[1040,2349,504],{},[1040,2351,702],{},[1040,2353,610],{},[1479,2355,2356,2361,2364],{},[1494,2357,2358],{},[1040,2359,2360],{},"program.drift_detected",[1494,2362,2363],{},"A field of a published version stopped filling, parsing or keeping its length, or its canary failed, before your next job notices.",[1494,2365,2366,1547,2368,1547,2371,1547,2374,1547,2376,1547,2379,1547,2381,1547,2384,2386,2387],{},[1040,2367,489],{},[1040,2369,2370],{},"program_version_id",[1040,2372,2373],{},"field",[1040,2375,2206],{},[1040,2377,2378],{},"detail",[1040,2380,727],{},[1040,2382,2383],{},"at",[1040,2385,478],{}," (canary only), ",[1040,2388,610],{},[1479,2390,2391,2396,2399],{},[1494,2392,2393],{},[1040,2394,2395],{},"credits.low",[1494,2397,2398],{},"Allowance consumption crossed 80 % or 95 %. Once per level per period.",[1494,2400,2401,1547,2404,1547,2407],{},[1040,2402,2403],{},"balance",[1040,2405,2406],{},"level",[1040,2408,610],{},[1479,2410,2411,2416,2419],{},[1494,2412,2413],{},[1040,2414,2415],{},"credits.exhausted",[1494,2417,2418],{},"Nothing more can be consumed. Once, until credits arrive.",[1494,2420,2421,1547,2423],{},[1040,2422,2403],{},[1040,2424,610],{},[1479,2426,2427,2432,2435],{},[1494,2428,2429],{},[1040,2430,2431],{},"credits.recharged",[1494,2433,2434],{},"Credits arrived after exhaustion, or an auto-recharge completed.",[1494,2436,2437,1547,2439],{},[1040,2438,2403],{},[1040,2440,610],{},[1479,2442,2443,2448,2451],{},[1494,2444,2445],{},[1040,2446,2447],{},"byo.unhealthy",[1494,2449,2450],{},"One of your own providers (proxy, model, captcha) started failing its health checks.",[1494,2452,2453,1547,2456,1547,2458,1547,2460,2262,2462],{},[1040,2454,2455],{},"byo",[1040,2457,2206],{},[1040,2459,2250],{},[1040,2461,2378],{},[1040,2463,610],{},[1479,2465,2466,2471,2474],{},[1494,2467,2468],{},[1040,2469,2470],{},"byo.recovered",[1494,2472,2473],{},"A provider that was failing or degraded passed two health checks in a row.",[1494,2475,2476,1547,2478,1547,2480,1547,2482],{},[1040,2477,2455],{},[1040,2479,2206],{},[1040,2481,2250],{},[1040,2483,610],{},[1479,2485,2486,2491,2494],{},[1494,2487,2488],{},[1040,2489,2490],{},"webhook.disabled",[1494,2492,2493],{},"Kaabist switched one of your endpoints off after a day of failed deliveries.",[1494,2495,2496,1547,2498,2037,2500,2041,2503,1547,2505,1547,2508],{},[1040,2497,513],{},[1040,2499,2036],{},[1040,2501,2502],{},"auto",[1040,2504,2333],{},[1040,2506,2507],{},"failing_since",[1040,2509,610],{},[1479,2511,2512,2516,2519],{},[1494,2513,2514],{},[1040,2515,1610],{},[1494,2517,2518],{},"You asked for a test delivery. Sent to that endpoint only; no pattern selects it.",[1494,2520,2521,1547,2523],{},[1040,2522,513],{},[1040,2524,610],{},[1025,2526,2527,2529,2530,2533,2534,2537,2538,2540,2541,2543],{},[1040,2528,489],{}," is ",[1040,2531,2532],{},"name@version"," of the program the job ran, or ",[1040,2535,2536],{},"null"," for a ",[1040,2539,610],{}," + ",[1040,2542,569],{}," job.",[1029,2545,2547],{"id":2546},"verifying-the-signature","Verifying the signature",[1025,2549,2550,2551,2554,2555,2558,2559,2562,2563,2565,2566,2568,2569,2571],{},"Every delivery carries ",[1040,2552,2553],{},"Kaab-Signature: t=\u003Cunix seconds>,v1=\u003Chex>",". ",[1040,2556,2557],{},"v1"," is the HMAC-SHA256,\nunder your endpoint's secret (the whole ",[1040,2560,2561],{},"whsec_…"," string), of the timestamp, a dot and the raw\nrequest body. For a day after you rotate a secret the header carries a second ",[1040,2564,2557],{},", made with the\nprevious secret, so receivers that still have the old one keep working while you update them.\nAccept a delivery when ",[1468,2567,634],{}," ",[1040,2570,2557],{}," matches and the timestamp is within five minutes of now;\nrefuse it otherwise.",[1025,2573,2574],{},"Two rules make or break this:",[2576,2577,2578,2585],"ul",{},[2579,2580,2581,2584],"li",{},[1468,2582,2583],{},"Verify the raw body."," Read the bytes as they arrived and verify those. A body that was parsed\nand serialized again will not match.",[2579,2586,2587,2590],{},[1468,2588,2589],{},"Compare in constant time."," Every example below does.",[1025,2592,2593],{},"The SDKs do both for you: the receiver below verifies with the SDK, answers at once and\ndeduplicates on the event id.",[1074,2595,2596,3002,3372],{},[1077,2597,2599],{"className":1079,"code":2598,"filename":1081,"language":1082,"meta":1083,"style":1083},"const server = createServer((request, response) => {\n  const chunks: Buffer[] = [];\n  request.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n  request.on(\"end\", () => {\n    try {\n      \u002F\u002F The raw bytes, before any JSON parser: the signature is over them.\n      const event = kaab.webhooks.constructEvent(\n        Buffer.concat(chunks),\n        request.headers[\"kaab-signature\"]?.toString(),\n        secret,\n      );\n      response.writeHead(204).end(); \u002F\u002F answer fast; work afterwards\n      if (seen.has(event.id)) return;\n      seen.add(event.id);\n      console.log(`verified ${event.type} ${event.id}`);\n      resolveFirst(event.type);\n    } catch (error) {\n      response.writeHead(error instanceof KaabSignatureError ? 401 : 400).end();\n      console.log(`refused a delivery: ${(error as Error).message}`);\n    }\n  });\n});\n",[1040,2600,2601,2626,2647,2682,2702,2709,2714,2736,2750,2771,2778,2783,2808,2836,2852,2893,2906,2918,2953,2984,2990,2996],{"__ignoreMap":1083},[1087,2602,2603,2605,2608,2610,2613,2616,2618,2621,2624],{"class":1089,"line":1090},[1087,2604,1094],{"class":1093},[1087,2606,2607],{"class":1097}," server",[1087,2609,1101],{"class":1093},[1087,2611,2612],{"class":1110}," createServer",[1087,2614,2615],{"class":1119},"((request",[1087,2617,1124],{"class":1123},[1087,2619,2620],{"class":1119}," response) ",[1087,2622,2623],{"class":1093},"=>",[1087,2625,1338],{"class":1119},[1087,2627,2628,2631,2634,2636,2639,2642,2644],{"class":1089,"line":1156},[1087,2629,2630],{"class":1093},"  const",[1087,2632,2633],{"class":1097}," chunks",[1087,2635,1130],{"class":1093},[1087,2637,2638],{"class":1110}," Buffer",[1087,2640,2641],{"class":1119},"[] ",[1087,2643,1162],{"class":1093},[1087,2645,2646],{"class":1119}," [];\n",[1087,2648,2649,2652,2655,2658,2660,2662,2665,2667,2669,2672,2674,2676,2679],{"class":1089,"line":1341},[1087,2650,2651],{"class":1097},"  request",[1087,2653,2654],{"class":1110},".on",[1087,2656,2657],{"class":1119},"(",[1087,2659,1860],{"class":1136},[1087,2661,1124],{"class":1123},[1087,2663,2664],{"class":1119}," (chunk",[1087,2666,1130],{"class":1093},[1087,2668,2638],{"class":1110},[1087,2670,2671],{"class":1119},") ",[1087,2673,2623],{"class":1093},[1087,2675,2633],{"class":1097},[1087,2677,2678],{"class":1110},".push",[1087,2680,2681],{"class":1119},"(chunk));\n",[1087,2683,2684,2686,2688,2690,2693,2695,2698,2700],{"class":1089,"line":1353},[1087,2685,2651],{"class":1097},[1087,2687,2654],{"class":1110},[1087,2689,2657],{"class":1119},[1087,2691,2692],{"class":1136},"\"end\"",[1087,2694,1124],{"class":1123},[1087,2696,2697],{"class":1119}," () ",[1087,2699,2623],{"class":1093},[1087,2701,1338],{"class":1119},[1087,2703,2704,2707],{"class":1089,"line":1359},[1087,2705,2706],{"class":1093},"    try",[1087,2708,1338],{"class":1119},[1087,2710,2711],{"class":1089,"line":1376},[1087,2712,2713],{"class":1170},"      \u002F\u002F The raw bytes, before any JSON parser: the signature is over them.\n",[1087,2715,2716,2719,2722,2724,2726,2728,2730,2733],{"class":1089,"line":1389},[1087,2717,2718],{"class":1093},"      const",[1087,2720,2721],{"class":1097}," event",[1087,2723,1101],{"class":1093},[1087,2725,1107],{"class":1097},[1087,2727,1111],{"class":1110},[1087,2729,105],{"class":1097},[1087,2731,2732],{"class":1110},".constructEvent",[1087,2734,2735],{"class":1119},"(\n",[1087,2737,2738,2741,2744,2747],{"class":1089,"line":1398},[1087,2739,2740],{"class":1097},"        Buffer",[1087,2742,2743],{"class":1110},".concat",[1087,2745,2746],{"class":1119},"(chunks)",[1087,2748,2749],{"class":1123},",\n",[1087,2751,2752,2755,2758,2761,2763,2766,2769],{"class":1089,"line":1403},[1087,2753,2754],{"class":1097},"        request",[1087,2756,2757],{"class":1119},".headers[",[1087,2759,2760],{"class":1136},"\"kaab-signature\"",[1087,2762,1140],{"class":1119},[1087,2764,2765],{"class":1110},"?.toString",[1087,2767,2768],{"class":1119},"()",[1087,2770,2749],{"class":1123},[1087,2772,2773,2776],{"class":1089,"line":1425},[1087,2774,2775],{"class":1119},"        secret",[1087,2777,2749],{"class":1123},[1087,2779,2780],{"class":1089,"line":1437},[1087,2781,2782],{"class":1119},"      );\n",[1087,2784,2785,2788,2791,2793,2796,2799,2802,2805],{"class":1089,"line":1449},[1087,2786,2787],{"class":1097},"      response",[1087,2789,2790],{"class":1110},".writeHead",[1087,2792,2657],{"class":1119},[1087,2794,2795],{"class":1097},"204",[1087,2797,2798],{"class":1119},")",[1087,2800,2801],{"class":1110},".end",[1087,2803,2804],{"class":1119},"(); ",[1087,2806,2807],{"class":1170},"\u002F\u002F answer fast; work afterwards\n",[1087,2809,2811,2814,2816,2819,2822,2824,2827,2830,2833],{"class":1089,"line":2810},13,[1087,2812,2813],{"class":1093},"      if",[1087,2815,2037],{"class":1119},[1087,2817,2818],{"class":1097},"seen",[1087,2820,2821],{"class":1110},".has",[1087,2823,2657],{"class":1119},[1087,2825,2826],{"class":1097},"event",[1087,2828,2829],{"class":1119},".id)) ",[1087,2831,2832],{"class":1093},"return",[1087,2834,2835],{"class":1119},";\n",[1087,2837,2839,2842,2845,2847,2849],{"class":1089,"line":2838},14,[1087,2840,2841],{"class":1097},"      seen",[1087,2843,2844],{"class":1110},".add",[1087,2846,2657],{"class":1119},[1087,2848,2826],{"class":1097},[1087,2850,2851],{"class":1119},".id);\n",[1087,2853,2855,2858,2861,2863,2866,2869,2871,2874,2877,2880,2882,2885,2887,2890],{"class":1089,"line":2854},15,[1087,2856,2857],{"class":1097},"      console",[1087,2859,2860],{"class":1110},".log",[1087,2862,2657],{"class":1119},[1087,2864,2865],{"class":1136},"`verified ",[1087,2867,2868],{"class":1093},"${",[1087,2870,2826],{"class":1097},[1087,2872,2873],{"class":1119},".type",[1087,2875,2876],{"class":1093},"}",[1087,2878,2879],{"class":1093}," ${",[1087,2881,2826],{"class":1097},[1087,2883,2884],{"class":1119},".id",[1087,2886,2876],{"class":1093},[1087,2888,2889],{"class":1136},"`",[1087,2891,2892],{"class":1119},");\n",[1087,2894,2896,2899,2901,2903],{"class":1089,"line":2895},16,[1087,2897,2898],{"class":1110},"      resolveFirst",[1087,2900,2657],{"class":1119},[1087,2902,2826],{"class":1097},[1087,2904,2905],{"class":1119},".type);\n",[1087,2907,2909,2912,2915],{"class":1089,"line":2908},17,[1087,2910,2911],{"class":1119},"    } ",[1087,2913,2914],{"class":1093},"catch",[1087,2916,2917],{"class":1119}," (error) {\n",[1087,2919,2921,2923,2925,2928,2931,2934,2937,2940,2943,2946,2948,2950],{"class":1089,"line":2920},18,[1087,2922,2787],{"class":1097},[1087,2924,2790],{"class":1110},[1087,2926,2927],{"class":1119},"(error ",[1087,2929,2930],{"class":1093},"instanceof",[1087,2932,2933],{"class":1110}," KaabSignatureError",[1087,2935,2936],{"class":1093}," ?",[1087,2938,2939],{"class":1097}," 401",[1087,2941,2942],{"class":1093}," :",[1087,2944,2945],{"class":1097}," 400",[1087,2947,2798],{"class":1119},[1087,2949,2801],{"class":1110},[1087,2951,2952],{"class":1119},"();\n",[1087,2954,2956,2958,2960,2962,2965,2967,2969,2972,2975,2978,2980,2982],{"class":1089,"line":2955},19,[1087,2957,2857],{"class":1097},[1087,2959,2860],{"class":1110},[1087,2961,2657],{"class":1119},[1087,2963,2964],{"class":1136},"`refused a delivery: ",[1087,2966,2868],{"class":1093},[1087,2968,2927],{"class":1119},[1087,2970,2971],{"class":1093},"as",[1087,2973,2974],{"class":1110}," Error",[1087,2976,2977],{"class":1119},").message",[1087,2979,2876],{"class":1093},[1087,2981,2889],{"class":1136},[1087,2983,2892],{"class":1119},[1087,2985,2987],{"class":1089,"line":2986},20,[1087,2988,2989],{"class":1119},"    }\n",[1087,2991,2993],{"class":1089,"line":2992},21,[1087,2994,2995],{"class":1119},"  });\n",[1087,2997,2999],{"class":1089,"line":2998},22,[1087,3000,3001],{"class":1119},"});\n",[1077,3003,3005],{"className":1174,"code":3004,"filename":1176,"language":1177,"meta":1083,"style":1083},"class Receiver(BaseHTTPRequestHandler):\n    def do_POST(self) -> None:  # noqa: N802\n        raw = self.rfile.read(int(self.headers.get(\"content-length\", \"0\")))  # the bytes, unparsed\n        try:\n            event = webhooks.construct_event(raw, self.headers.get(\"kaab-signature\"), secret)\n        except KaabSignatureError as error:\n            self.send_response(401)\n            self.end_headers()\n            print(f\"refused a delivery: {error}\")\n            return\n        self.send_response(204)  # answer fast; work afterwards\n        self.end_headers()\n        if event[\"id\"] not in seen:\n            seen.add(event[\"id\"])\n            print(f\"verified {event['type']} {event['id']}\")\n            received.put(event[\"type\"])\n\n    def log_message(self, *_: object) -> None:\n        pass\n",[1040,3006,3007,3023,3051,3098,3106,3133,3148,3165,3176,3201,3206,3224,3234,3258,3276,3315,3331,3335,3367],{"__ignoreMap":1083},[1087,3008,3009,3012,3015,3017,3020],{"class":1089,"line":1090},[1087,3010,3011],{"class":1093},"class",[1087,3013,3014],{"class":1110}," Receiver",[1087,3016,2657],{"class":1119},[1087,3018,3019],{"class":1110},"BaseHTTPRequestHandler",[1087,3021,3022],{"class":1119},"):\n",[1087,3024,3025,3028,3031,3033,3037,3039,3042,3045,3048],{"class":1089,"line":1156},[1087,3026,3027],{"class":1093},"    def",[1087,3029,3030],{"class":1110}," do_POST",[1087,3032,2657],{"class":1119},[1087,3034,3036],{"class":3035},"sIH4l","self",[1087,3038,2671],{"class":1119},[1087,3040,3041],{"class":1123},"->",[1087,3043,3044],{"class":1097}," None",[1087,3046,3047],{"class":1119},":  ",[1087,3049,3050],{"class":1170},"# noqa: N802\n",[1087,3052,3053,3056,3058,3061,3063,3066,3068,3071,3073,3076,3079,3082,3084,3087,3089,3092,3095],{"class":1089,"line":1341},[1087,3054,3055],{"class":1119},"        raw ",[1087,3057,1162],{"class":1093},[1087,3059,3060],{"class":1119}," self",[1087,3062,1111],{"class":1123},[1087,3064,3065],{"class":1119},"rfile",[1087,3067,1111],{"class":1123},[1087,3069,3070],{"class":1110},"read",[1087,3072,2657],{"class":1123},[1087,3074,3075],{"class":1097},"int",[1087,3077,3078],{"class":1123},"(self.headers.",[1087,3080,3081],{"class":1110},"get",[1087,3083,2657],{"class":1123},[1087,3085,3086],{"class":1136},"\"content-length\"",[1087,3088,1547],{"class":1123},[1087,3090,3091],{"class":1136},"\"0\"",[1087,3093,3094],{"class":1123},")))",[1087,3096,3097],{"class":1170},"  # the bytes, unparsed\n",[1087,3099,3100,3103],{"class":1089,"line":1353},[1087,3101,3102],{"class":1093},"        try",[1087,3104,3105],{"class":1123},":\n",[1087,3107,3108,3111,3113,3116,3118,3121,3124,3126,3128,3130],{"class":1089,"line":1359},[1087,3109,3110],{"class":1119},"            event ",[1087,3112,1162],{"class":1093},[1087,3114,3115],{"class":1119}," webhooks",[1087,3117,1111],{"class":1123},[1087,3119,3120],{"class":1110},"construct_event",[1087,3122,3123],{"class":1123},"(raw, self.headers.",[1087,3125,3081],{"class":1110},[1087,3127,2657],{"class":1123},[1087,3129,2760],{"class":1136},[1087,3131,3132],{"class":1123},"), secret)\n",[1087,3134,3135,3138,3141,3143,3146],{"class":1089,"line":1376},[1087,3136,3137],{"class":1093},"        except",[1087,3139,3140],{"class":1119}," KaabSignatureError ",[1087,3142,2971],{"class":1093},[1087,3144,3145],{"class":1119}," error",[1087,3147,3105],{"class":1123},[1087,3149,3150,3153,3155,3158,3160,3163],{"class":1089,"line":1389},[1087,3151,3152],{"class":1119},"            self",[1087,3154,1111],{"class":1123},[1087,3156,3157],{"class":1110},"send_response",[1087,3159,2657],{"class":1123},[1087,3161,3162],{"class":1097},"401",[1087,3164,1223],{"class":1123},[1087,3166,3167,3169,3171,3174],{"class":1089,"line":1398},[1087,3168,3152],{"class":1119},[1087,3170,1111],{"class":1123},[1087,3172,3173],{"class":1110},"end_headers",[1087,3175,1434],{"class":1123},[1087,3177,3178,3181,3183,3186,3189,3191,3194,3196,3199],{"class":1089,"line":1403},[1087,3179,3180],{"class":1110},"            print",[1087,3182,2657],{"class":1123},[1087,3184,3185],{"class":1093},"f",[1087,3187,3188],{"class":1136},"\"refused a delivery: ",[1087,3190,1289],{"class":1097},[1087,3192,3193],{"class":1123},"error",[1087,3195,2876],{"class":1097},[1087,3197,3198],{"class":1136},"\"",[1087,3200,1223],{"class":1123},[1087,3202,3203],{"class":1089,"line":1425},[1087,3204,3205],{"class":1093},"            return\n",[1087,3207,3208,3211,3213,3215,3217,3219,3221],{"class":1089,"line":1437},[1087,3209,3210],{"class":1119},"        self",[1087,3212,1111],{"class":1123},[1087,3214,3157],{"class":1110},[1087,3216,2657],{"class":1123},[1087,3218,2795],{"class":1097},[1087,3220,2798],{"class":1123},[1087,3222,3223],{"class":1170},"  # answer fast; work afterwards\n",[1087,3225,3226,3228,3230,3232],{"class":1089,"line":1449},[1087,3227,3210],{"class":1119},[1087,3229,1111],{"class":1123},[1087,3231,3173],{"class":1110},[1087,3233,1434],{"class":1123},[1087,3235,3236,3239,3241,3243,3245,3247,3250,3253,3256],{"class":1089,"line":2810},[1087,3237,3238],{"class":1093},"        if",[1087,3240,2721],{"class":1119},[1087,3242,1210],{"class":1123},[1087,3244,1810],{"class":1136},[1087,3246,1140],{"class":1123},[1087,3248,3249],{"class":1093}," not",[1087,3251,3252],{"class":1093}," in",[1087,3254,3255],{"class":1119}," seen",[1087,3257,3105],{"class":1123},[1087,3259,3260,3263,3265,3268,3271,3273],{"class":1089,"line":2838},[1087,3261,3262],{"class":1119},"            seen",[1087,3264,1111],{"class":1123},[1087,3266,3267],{"class":1110},"add",[1087,3269,3270],{"class":1123},"(event[",[1087,3272,1810],{"class":1136},[1087,3274,3275],{"class":1123},"])\n",[1087,3277,3278,3280,3282,3284,3287,3289,3292,3295,3297,3299,3302,3304,3307,3309,3311,3313],{"class":1089,"line":2854},[1087,3279,3180],{"class":1110},[1087,3281,2657],{"class":1123},[1087,3283,3185],{"class":1093},[1087,3285,3286],{"class":1136},"\"verified ",[1087,3288,1289],{"class":1097},[1087,3290,3291],{"class":1123},"event[",[1087,3293,3294],{"class":1136},"'type'",[1087,3296,1140],{"class":1123},[1087,3298,2876],{"class":1097},[1087,3300,3301],{"class":1097}," {",[1087,3303,3291],{"class":1123},[1087,3305,3306],{"class":1136},"'id'",[1087,3308,1140],{"class":1123},[1087,3310,2876],{"class":1097},[1087,3312,3198],{"class":1136},[1087,3314,1223],{"class":1123},[1087,3316,3317,3320,3322,3325,3327,3329],{"class":1089,"line":2895},[1087,3318,3319],{"class":1119},"            received",[1087,3321,1111],{"class":1123},[1087,3323,3324],{"class":1110},"put",[1087,3326,3270],{"class":1123},[1087,3328,1820],{"class":1136},[1087,3330,3275],{"class":1123},[1087,3332,3333],{"class":1089,"line":2908},[1087,3334,1803],{"emptyLinePlaceholder":1802},[1087,3336,3337,3339,3342,3344,3346,3348,3351,3354,3356,3359,3361,3363,3365],{"class":1089,"line":2920},[1087,3338,3027],{"class":1093},[1087,3340,3341],{"class":1110}," log_message",[1087,3343,2657],{"class":1119},[1087,3345,3036],{"class":3035},[1087,3347,1124],{"class":1123},[1087,3349,3350],{"class":1093}," *",[1087,3352,3353],{"class":3035},"_",[1087,3355,1130],{"class":1123},[1087,3357,3358],{"class":1097}," object",[1087,3360,2671],{"class":1119},[1087,3362,3041],{"class":1123},[1087,3364,3044],{"class":1097},[1087,3366,3105],{"class":1119},[1087,3368,3369],{"class":1089,"line":2955},[1087,3370,3371],{"class":1093},"        pass\n",[1077,3373,3375],{"className":1242,"code":3374,"filename":1244,"language":1245,"meta":1083,"style":1083},"handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    raw, err := io.ReadAll(r.Body) \u002F\u002F the bytes, before any JSON decoding\n    if err != nil {\n        w.WriteHeader(http.StatusBadRequest)\n        return\n    }\n    mu.Lock()\n    key := secret\n    mu.Unlock()\n    event, err := webhooks.ConstructEvent(raw, r.Header.Get(webhooks.SignatureHeader), key)\n    if err != nil {\n        w.WriteHeader(http.StatusUnauthorized)\n        log.Printf(\"refused a delivery: %v\", err)\n        return\n    }\n    w.WriteHeader(http.StatusNoContent) \u002F\u002F answer fast; work afterwards\n    mu.Lock()\n    defer mu.Unlock()\n    if !seen[event.ID] {\n        seen[event.ID] = true\n        fmt.Printf(\"verified %s %s\\n\", event.Type, event.ID)\n        received \u003C- event.Type\n    }\n})\n",[1040,3376,3377,3420,3439,3452,3463,3468,3472,3481,3491,3499,3521,3533,3542,3562,3566,3570,3582,3590,3602,3612,3622,3645,3656,3661],{"__ignoreMap":1083},[1087,3378,3379,3382,3384,3387,3390,3392,3395,3398,3400,3402,3405,3408,3410,3412,3414,3417],{"class":1089,"line":1090},[1087,3380,3381],{"class":1119},"handler ",[1087,3383,1255],{"class":1093},[1087,3385,3386],{"class":1119}," http.",[1087,3388,3389],{"class":1110},"HandlerFunc",[1087,3391,2657],{"class":1119},[1087,3393,3394],{"class":1093},"func",[1087,3396,3397],{"class":1119},"(w ",[1087,3399,1727],{"class":1110},[1087,3401,1111],{"class":1119},[1087,3403,3404],{"class":1110},"ResponseWriter",[1087,3406,3407],{"class":1119},", r ",[1087,3409,1533],{"class":1093},[1087,3411,1727],{"class":1110},[1087,3413,1111],{"class":1119},[1087,3415,3416],{"class":1110},"Request",[1087,3418,3419],{"class":1119},") {\n",[1087,3421,3422,3425,3427,3430,3433,3436],{"class":1089,"line":1156},[1087,3423,3424],{"class":1119},"    raw, err ",[1087,3426,1255],{"class":1093},[1087,3428,3429],{"class":1119}," io.",[1087,3431,3432],{"class":1110},"ReadAll",[1087,3434,3435],{"class":1119},"(r.Body) ",[1087,3437,3438],{"class":1170},"\u002F\u002F the bytes, before any JSON decoding\n",[1087,3440,3441,3444,3446,3448,3450],{"class":1089,"line":1341},[1087,3442,3443],{"class":1093},"    if",[1087,3445,1329],{"class":1119},[1087,3447,1332],{"class":1093},[1087,3449,1335],{"class":1097},[1087,3451,1338],{"class":1119},[1087,3453,3454,3457,3460],{"class":1089,"line":1353},[1087,3455,3456],{"class":1119},"        w.",[1087,3458,3459],{"class":1110},"WriteHeader",[1087,3461,3462],{"class":1119},"(http.StatusBadRequest)\n",[1087,3464,3465],{"class":1089,"line":1359},[1087,3466,3467],{"class":1093},"        return\n",[1087,3469,3470],{"class":1089,"line":1376},[1087,3471,2989],{"class":1119},[1087,3473,3474,3477,3479],{"class":1089,"line":1389},[1087,3475,3476],{"class":1119},"    mu.",[1087,3478,1431],{"class":1110},[1087,3480,1434],{"class":1119},[1087,3482,3483,3486,3488],{"class":1089,"line":1398},[1087,3484,3485],{"class":1119},"    key ",[1087,3487,1255],{"class":1093},[1087,3489,3490],{"class":1119}," secret\n",[1087,3492,3493,3495,3497],{"class":1089,"line":1403},[1087,3494,3476],{"class":1119},[1087,3496,1454],{"class":1110},[1087,3498,1434],{"class":1119},[1087,3500,3501,3504,3506,3509,3512,3515,3518],{"class":1089,"line":1425},[1087,3502,3503],{"class":1119},"    event, err ",[1087,3505,1255],{"class":1093},[1087,3507,3508],{"class":1119}," webhooks.",[1087,3510,3511],{"class":1110},"ConstructEvent",[1087,3513,3514],{"class":1119},"(raw, r.Header.",[1087,3516,3517],{"class":1110},"Get",[1087,3519,3520],{"class":1119},"(webhooks.SignatureHeader), key)\n",[1087,3522,3523,3525,3527,3529,3531],{"class":1089,"line":1437},[1087,3524,3443],{"class":1093},[1087,3526,1329],{"class":1119},[1087,3528,1332],{"class":1093},[1087,3530,1335],{"class":1097},[1087,3532,1338],{"class":1119},[1087,3534,3535,3537,3539],{"class":1089,"line":1449},[1087,3536,3456],{"class":1119},[1087,3538,3459],{"class":1110},[1087,3540,3541],{"class":1119},"(http.StatusUnauthorized)\n",[1087,3543,3544,3547,3550,3552,3554,3557,3559],{"class":1089,"line":2810},[1087,3545,3546],{"class":1119},"        log.",[1087,3548,3549],{"class":1110},"Printf",[1087,3551,2657],{"class":1119},[1087,3553,3188],{"class":1136},[1087,3555,3556],{"class":1097},"%v",[1087,3558,3198],{"class":1136},[1087,3560,3561],{"class":1119},", err)\n",[1087,3563,3564],{"class":1089,"line":2838},[1087,3565,3467],{"class":1093},[1087,3567,3568],{"class":1089,"line":2854},[1087,3569,2989],{"class":1119},[1087,3571,3572,3575,3577,3580],{"class":1089,"line":2895},[1087,3573,3574],{"class":1119},"    w.",[1087,3576,3459],{"class":1110},[1087,3578,3579],{"class":1119},"(http.StatusNoContent) ",[1087,3581,2807],{"class":1170},[1087,3583,3584,3586,3588],{"class":1089,"line":2908},[1087,3585,3476],{"class":1119},[1087,3587,1431],{"class":1110},[1087,3589,1434],{"class":1119},[1087,3591,3592,3595,3598,3600],{"class":1089,"line":2920},[1087,3593,3594],{"class":1093},"    defer",[1087,3596,3597],{"class":1119}," mu.",[1087,3599,1454],{"class":1110},[1087,3601,1434],{"class":1119},[1087,3603,3604,3606,3609],{"class":1089,"line":2955},[1087,3605,3443],{"class":1093},[1087,3607,3608],{"class":1093}," !",[1087,3610,3611],{"class":1119},"seen[event.ID] {\n",[1087,3613,3614,3617,3619],{"class":1089,"line":2986},[1087,3615,3616],{"class":1119},"        seen[event.ID] ",[1087,3618,1162],{"class":1093},[1087,3620,3621],{"class":1097}," true\n",[1087,3623,3624,3627,3629,3631,3633,3636,3639,3642],{"class":1089,"line":2992},[1087,3625,3626],{"class":1119},"        fmt.",[1087,3628,3549],{"class":1110},[1087,3630,2657],{"class":1119},[1087,3632,3286],{"class":1136},[1087,3634,3635],{"class":1097},"%s",[1087,3637,3638],{"class":1097}," %s",[1087,3640,3641],{"class":1136},"\\n\"",[1087,3643,3644],{"class":1119},", event.Type, event.ID)\n",[1087,3646,3647,3650,3653],{"class":1089,"line":2998},[1087,3648,3649],{"class":1119},"        received ",[1087,3651,3652],{"class":1093},"\u003C-",[1087,3654,3655],{"class":1119}," event.Type\n",[1087,3657,3659],{"class":1089,"line":3658},23,[1087,3660,2989],{"class":1119},[1087,3662,3664],{"class":1089,"line":3663},24,[1087,3665,1321],{"class":1119},[1613,3667,3669],{"id":3668},"without-an-sdk","Without an SDK",[1025,3671,3672,3673,1547,3676,3679,3680,3683],{},"These functions use the language's standard library only. They are the files\n",[1040,3674,3675],{},"examples\u002Fwebhooks\u002Fnode\u002Fverify.ts",[1040,3677,3678],{},"examples\u002Fwebhooks\u002Fpython\u002Fverify.py"," and\n",[1040,3681,3682],{},"examples\u002Fwebhooks\u002Fgo\u002Fverify.go","; Kaabist's test suite runs them against real\ndeliveries on every change. Each directory also holds a small receiver that verifies, answers and\ndeduplicates.",[1074,3685,3686,4250,4838],{},[1077,3687,3689],{"className":1079,"code":3688,"filename":1081,"language":1082,"meta":1083,"style":1083},"import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n\u002F** Reject deliveries whose timestamp is more than five minutes from now. *\u002F\nexport const TOLERANCE_SECONDS = 300;\n\n\u002F**\n * Verify a Kaab webhook.\n *\n * `header` is the `Kaab-Signature` request header, `rawBody` the request body exactly as it\n * arrived (read it before any JSON parser touches it), `secret` the endpoint's `whsec_…` secret.\n * The header is `t=\u003Cunix seconds>,v1=\u003Chex>`, with a second `v1` for a day after a secret\n * rotation; the delivery is genuine when any `v1` is the HMAC-SHA256 of `\"\u003Ct>.\u003CrawBody>\"`.\n *\u002F\nexport function verifyKaabSignature(\n  header: string | undefined,\n  rawBody: string,\n  secret: string,\n  nowSeconds: number = Math.floor(Date.now() \u002F 1000),\n): boolean {\n  if (header === undefined) return false;\n  const [stamp, ...signatures] = header.split(\",\");\n  if (stamp === undefined || !stamp.startsWith(\"t=\")) return false;\n  const timestamp = Number(stamp.slice(2));\n  if (!Number.isInteger(timestamp)) return false;\n  if (Math.abs(nowSeconds - timestamp) > TOLERANCE_SECONDS) return false;\n\n  const expected = createHmac(\"sha256\", secret).update(`${timestamp}.${rawBody}`).digest();\n  return signatures.some((part) => {\n    if (!part.startsWith(\"v1=\")) return false;\n    const given = Buffer.from(part.slice(3), \"hex\");\n    return given.length === expected.length && timingSafeEqual(given, expected);\n  });\n}\n",[1040,3690,3691,3712,3716,3721,3739,3743,3748,3753,3758,3763,3768,3773,3778,3783,3795,3813,3824,3835,3873,3884,3906,3941,3976,4003,4027,4062,4067,4122,4141,4168,4204,4240,4245],{"__ignoreMap":1083},[1087,3692,3693,3696,3699,3701,3704,3707,3710],{"class":1089,"line":1090},[1087,3694,3695],{"class":1093},"import",[1087,3697,3698],{"class":1119}," { createHmac",[1087,3700,1124],{"class":1123},[1087,3702,3703],{"class":1119}," timingSafeEqual } ",[1087,3705,3706],{"class":1093},"from",[1087,3708,3709],{"class":1136}," \"node:crypto\"",[1087,3711,2835],{"class":1119},[1087,3713,3714],{"class":1089,"line":1156},[1087,3715,1803],{"emptyLinePlaceholder":1802},[1087,3717,3718],{"class":1089,"line":1341},[1087,3719,3720],{"class":1170},"\u002F** Reject deliveries whose timestamp is more than five minutes from now. *\u002F\n",[1087,3722,3723,3726,3729,3732,3734,3737],{"class":1089,"line":1353},[1087,3724,3725],{"class":1093},"export",[1087,3727,3728],{"class":1093}," const",[1087,3730,3731],{"class":1097}," TOLERANCE_SECONDS",[1087,3733,1101],{"class":1093},[1087,3735,3736],{"class":1097}," 300",[1087,3738,2835],{"class":1119},[1087,3740,3741],{"class":1089,"line":1359},[1087,3742,1803],{"emptyLinePlaceholder":1802},[1087,3744,3745],{"class":1089,"line":1376},[1087,3746,3747],{"class":1170},"\u002F**\n",[1087,3749,3750],{"class":1089,"line":1389},[1087,3751,3752],{"class":1170}," * Verify a Kaab webhook.\n",[1087,3754,3755],{"class":1089,"line":1398},[1087,3756,3757],{"class":1170}," *\n",[1087,3759,3760],{"class":1089,"line":1403},[1087,3761,3762],{"class":1170}," * `header` is the `Kaab-Signature` request header, `rawBody` the request body exactly as it\n",[1087,3764,3765],{"class":1089,"line":1425},[1087,3766,3767],{"class":1170}," * arrived (read it before any JSON parser touches it), `secret` the endpoint's `whsec_…` secret.\n",[1087,3769,3770],{"class":1089,"line":1437},[1087,3771,3772],{"class":1170}," * The header is `t=\u003Cunix seconds>,v1=\u003Chex>`, with a second `v1` for a day after a secret\n",[1087,3774,3775],{"class":1089,"line":1449},[1087,3776,3777],{"class":1170}," * rotation; the delivery is genuine when any `v1` is the HMAC-SHA256 of `\"\u003Ct>.\u003CrawBody>\"`.\n",[1087,3779,3780],{"class":1089,"line":2810},[1087,3781,3782],{"class":1170}," *\u002F\n",[1087,3784,3785,3787,3790,3793],{"class":1089,"line":2838},[1087,3786,3725],{"class":1093},[1087,3788,3789],{"class":1093}," function",[1087,3791,3792],{"class":1110}," verifyKaabSignature",[1087,3794,2735],{"class":1119},[1087,3796,3797,3800,3802,3805,3808,3811],{"class":1089,"line":2854},[1087,3798,3799],{"class":1119},"  header",[1087,3801,1130],{"class":1093},[1087,3803,3804],{"class":1097}," string",[1087,3806,3807],{"class":1093}," |",[1087,3809,3810],{"class":1097}," undefined",[1087,3812,2749],{"class":1123},[1087,3814,3815,3818,3820,3822],{"class":1089,"line":2895},[1087,3816,3817],{"class":1119},"  rawBody",[1087,3819,1130],{"class":1093},[1087,3821,3804],{"class":1097},[1087,3823,2749],{"class":1123},[1087,3825,3826,3829,3831,3833],{"class":1089,"line":2908},[1087,3827,3828],{"class":1119},"  secret",[1087,3830,1130],{"class":1093},[1087,3832,3804],{"class":1097},[1087,3834,2749],{"class":1123},[1087,3836,3837,3840,3842,3845,3847,3850,3853,3855,3858,3861,3864,3866,3869,3871],{"class":1089,"line":2920},[1087,3838,3839],{"class":1119},"  nowSeconds",[1087,3841,1130],{"class":1093},[1087,3843,3844],{"class":1097}," number",[1087,3846,1101],{"class":1093},[1087,3848,3849],{"class":1097}," Math",[1087,3851,3852],{"class":1110},".floor",[1087,3854,2657],{"class":1119},[1087,3856,3857],{"class":1097},"Date",[1087,3859,3860],{"class":1110},".now",[1087,3862,3863],{"class":1119},"() ",[1087,3865,1743],{"class":1093},[1087,3867,3868],{"class":1097}," 1000",[1087,3870,2798],{"class":1119},[1087,3872,2749],{"class":1123},[1087,3874,3875,3877,3879,3882],{"class":1089,"line":2955},[1087,3876,2798],{"class":1119},[1087,3878,1130],{"class":1093},[1087,3880,3881],{"class":1097}," boolean",[1087,3883,1338],{"class":1119},[1087,3885,3886,3889,3892,3895,3897,3899,3901,3904],{"class":1089,"line":2986},[1087,3887,3888],{"class":1093},"  if",[1087,3890,3891],{"class":1119}," (header ",[1087,3893,3894],{"class":1093},"===",[1087,3896,3810],{"class":1097},[1087,3898,2671],{"class":1119},[1087,3900,2832],{"class":1093},[1087,3902,3903],{"class":1097}," false",[1087,3905,2835],{"class":1119},[1087,3907,3908,3910,3912,3915,3917,3920,3923,3926,3928,3931,3934,3936,3939],{"class":1089,"line":2992},[1087,3909,2630],{"class":1093},[1087,3911,1133],{"class":1119},[1087,3913,3914],{"class":1097},"stamp",[1087,3916,1124],{"class":1123},[1087,3918,3919],{"class":1093}," ...",[1087,3921,3922],{"class":1097},"signatures",[1087,3924,3925],{"class":1119},"] ",[1087,3927,1162],{"class":1093},[1087,3929,3930],{"class":1097}," header",[1087,3932,3933],{"class":1110},".split",[1087,3935,2657],{"class":1119},[1087,3937,3938],{"class":1136},"\",\"",[1087,3940,2892],{"class":1119},[1087,3942,3943,3945,3948,3950,3952,3955,3957,3959,3962,3964,3967,3970,3972,3974],{"class":1089,"line":2998},[1087,3944,3888],{"class":1093},[1087,3946,3947],{"class":1119}," (stamp ",[1087,3949,3894],{"class":1093},[1087,3951,3810],{"class":1097},[1087,3953,3954],{"class":1093}," ||",[1087,3956,3608],{"class":1093},[1087,3958,3914],{"class":1097},[1087,3960,3961],{"class":1110},".startsWith",[1087,3963,2657],{"class":1119},[1087,3965,3966],{"class":1136},"\"t=\"",[1087,3968,3969],{"class":1119},")) ",[1087,3971,2832],{"class":1093},[1087,3973,3903],{"class":1097},[1087,3975,2835],{"class":1119},[1087,3977,3978,3980,3983,3985,3988,3990,3992,3995,3997,4000],{"class":1089,"line":3658},[1087,3979,2630],{"class":1093},[1087,3981,3982],{"class":1097}," timestamp",[1087,3984,1101],{"class":1093},[1087,3986,3987],{"class":1110}," Number",[1087,3989,2657],{"class":1119},[1087,3991,3914],{"class":1097},[1087,3993,3994],{"class":1110},".slice",[1087,3996,2657],{"class":1119},[1087,3998,3999],{"class":1097},"2",[1087,4001,4002],{"class":1119},"));\n",[1087,4004,4005,4007,4009,4012,4015,4018,4021,4023,4025],{"class":1089,"line":3663},[1087,4006,3888],{"class":1093},[1087,4008,2037],{"class":1119},[1087,4010,4011],{"class":1093},"!",[1087,4013,4014],{"class":1097},"Number",[1087,4016,4017],{"class":1110},".isInteger",[1087,4019,4020],{"class":1119},"(timestamp)) ",[1087,4022,2832],{"class":1093},[1087,4024,3903],{"class":1097},[1087,4026,2835],{"class":1119},[1087,4028,4030,4032,4034,4037,4040,4043,4046,4049,4052,4054,4056,4058,4060],{"class":1089,"line":4029},25,[1087,4031,3888],{"class":1093},[1087,4033,2037],{"class":1119},[1087,4035,4036],{"class":1097},"Math",[1087,4038,4039],{"class":1110},".abs",[1087,4041,4042],{"class":1119},"(nowSeconds ",[1087,4044,4045],{"class":1093},"-",[1087,4047,4048],{"class":1119}," timestamp) ",[1087,4050,4051],{"class":1093},">",[1087,4053,3731],{"class":1097},[1087,4055,2671],{"class":1119},[1087,4057,2832],{"class":1093},[1087,4059,3903],{"class":1097},[1087,4061,2835],{"class":1119},[1087,4063,4065],{"class":1089,"line":4064},26,[1087,4066,1803],{"emptyLinePlaceholder":1802},[1087,4068,4070,4072,4075,4077,4080,4082,4085,4087,4090,4093,4095,4097,4099,4102,4104,4106,4108,4111,4113,4115,4117,4120],{"class":1089,"line":4069},27,[1087,4071,2630],{"class":1093},[1087,4073,4074],{"class":1097}," expected",[1087,4076,1101],{"class":1093},[1087,4078,4079],{"class":1110}," createHmac",[1087,4081,2657],{"class":1119},[1087,4083,4084],{"class":1136},"\"sha256\"",[1087,4086,1124],{"class":1123},[1087,4088,4089],{"class":1119}," secret)",[1087,4091,4092],{"class":1110},".update",[1087,4094,2657],{"class":1119},[1087,4096,2889],{"class":1136},[1087,4098,2868],{"class":1093},[1087,4100,4101],{"class":1119},"timestamp",[1087,4103,2876],{"class":1093},[1087,4105,1111],{"class":1136},[1087,4107,2868],{"class":1093},[1087,4109,4110],{"class":1119},"rawBody",[1087,4112,2876],{"class":1093},[1087,4114,2889],{"class":1136},[1087,4116,2798],{"class":1119},[1087,4118,4119],{"class":1110},".digest",[1087,4121,2952],{"class":1119},[1087,4123,4125,4128,4131,4134,4137,4139],{"class":1089,"line":4124},28,[1087,4126,4127],{"class":1093},"  return",[1087,4129,4130],{"class":1097}," signatures",[1087,4132,4133],{"class":1110},".some",[1087,4135,4136],{"class":1119},"((part) ",[1087,4138,2623],{"class":1093},[1087,4140,1338],{"class":1119},[1087,4142,4144,4146,4148,4150,4153,4155,4157,4160,4162,4164,4166],{"class":1089,"line":4143},29,[1087,4145,3443],{"class":1093},[1087,4147,2037],{"class":1119},[1087,4149,4011],{"class":1093},[1087,4151,4152],{"class":1097},"part",[1087,4154,3961],{"class":1110},[1087,4156,2657],{"class":1119},[1087,4158,4159],{"class":1136},"\"v1=\"",[1087,4161,3969],{"class":1119},[1087,4163,2832],{"class":1093},[1087,4165,3903],{"class":1097},[1087,4167,2835],{"class":1119},[1087,4169,4171,4174,4177,4179,4181,4184,4186,4188,4190,4192,4195,4197,4199,4202],{"class":1089,"line":4170},30,[1087,4172,4173],{"class":1093},"    const",[1087,4175,4176],{"class":1097}," given",[1087,4178,1101],{"class":1093},[1087,4180,2638],{"class":1097},[1087,4182,4183],{"class":1110},".from",[1087,4185,2657],{"class":1119},[1087,4187,4152],{"class":1097},[1087,4189,3994],{"class":1110},[1087,4191,2657],{"class":1119},[1087,4193,4194],{"class":1097},"3",[1087,4196,2798],{"class":1119},[1087,4198,1124],{"class":1123},[1087,4200,4201],{"class":1136}," \"hex\"",[1087,4203,2892],{"class":1119},[1087,4205,4207,4210,4212,4214,4217,4220,4222,4224,4226,4229,4232,4235,4237],{"class":1089,"line":4206},31,[1087,4208,4209],{"class":1093},"    return",[1087,4211,4176],{"class":1097},[1087,4213,1111],{"class":1119},[1087,4215,4216],{"class":1097},"length",[1087,4218,4219],{"class":1093}," ===",[1087,4221,4074],{"class":1097},[1087,4223,1111],{"class":1119},[1087,4225,4216],{"class":1097},[1087,4227,4228],{"class":1093}," &&",[1087,4230,4231],{"class":1110}," timingSafeEqual",[1087,4233,4234],{"class":1119},"(given",[1087,4236,1124],{"class":1123},[1087,4238,4239],{"class":1119}," expected);\n",[1087,4241,4243],{"class":1089,"line":4242},32,[1087,4244,2995],{"class":1119},[1087,4246,4248],{"class":1089,"line":4247},33,[1087,4249,1356],{"class":1119},[1077,4251,4253],{"className":1174,"code":4252,"filename":1176,"language":1177,"meta":1083,"style":1083},"import hashlib\nimport hmac\nimport json\nimport sys\nimport time\n\nTOLERANCE_SECONDS = 300\n\n\ndef verify_kaab_signature(header, raw_body, secret, now=None):\n    \"\"\"True when `header` (the Kaab-Signature header) signs `raw_body` under `secret`.\n\n    `raw_body` is the request body exactly as it arrived, as bytes or str. The header is\n    `t=\u003Cunix seconds>,v1=\u003Chex>`, with a second `v1` for a day after a secret rotation; the\n    delivery is genuine when any `v1` is the HMAC-SHA256 of \"\u003Ct>.\u003Craw body>\".\n    \"\"\"\n    if not header:\n        return False\n    parts = header.split(\",\")\n    if not parts[0].startswith(\"t=\"):\n        return False\n    try:\n        timestamp = int(parts[0][2:])\n    except ValueError:\n        return False\n    now = int(time.time()) if now is None else now\n    if abs(now - timestamp) > TOLERANCE_SECONDS:\n        return False\n\n    body = raw_body.encode(\"utf-8\") if isinstance(raw_body, str) else raw_body\n    signed = str(timestamp).encode(\"ascii\") + b\".\" + body\n    expected = hmac.new(secret.encode(\"utf-8\"), signed, hashlib.sha256).hexdigest()\n    return any(\n        part.startswith(\"v1=\") and hmac.compare_digest(part[3:], expected) for part in parts[1:]\n    )\n\n\nif __name__ == \"__main__\":\n    # Test harness: `{ \"secret\", \"signature\", \"body\", \"now\" }` as JSON on standard input.\n    data = json.load(sys.stdin)\n    ok = verify_kaab_signature(data[\"signature\"], data[\"body\"], data[\"secret\"], data[\"now\"])\n    print(\"valid\" if ok else \"invalid\")\n",[1040,4254,4255,4262,4269,4276,4283,4290,4294,4304,4308,4312,4347,4352,4356,4361,4366,4371,4376,4386,4394,4414,4440,4446,4452,4475,4485,4491,4526,4548,4554,4558,4597,4633,4665,4674,4723,4729,4734,4739,4755,4761,4780,4814],{"__ignoreMap":1083},[1087,4256,4257,4259],{"class":1089,"line":1090},[1087,4258,3695],{"class":1093},[1087,4260,4261],{"class":1119}," hashlib\n",[1087,4263,4264,4266],{"class":1089,"line":1156},[1087,4265,3695],{"class":1093},[1087,4267,4268],{"class":1119}," hmac\n",[1087,4270,4271,4273],{"class":1089,"line":1341},[1087,4272,3695],{"class":1093},[1087,4274,4275],{"class":1119}," json\n",[1087,4277,4278,4280],{"class":1089,"line":1353},[1087,4279,3695],{"class":1093},[1087,4281,4282],{"class":1119}," sys\n",[1087,4284,4285,4287],{"class":1089,"line":1359},[1087,4286,3695],{"class":1093},[1087,4288,4289],{"class":1119}," time\n",[1087,4291,4292],{"class":1089,"line":1376},[1087,4293,1803],{"emptyLinePlaceholder":1802},[1087,4295,4296,4299,4301],{"class":1089,"line":1389},[1087,4297,4298],{"class":1119},"TOLERANCE_SECONDS ",[1087,4300,1162],{"class":1093},[1087,4302,4303],{"class":1097}," 300\n",[1087,4305,4306],{"class":1089,"line":1398},[1087,4307,1803],{"emptyLinePlaceholder":1802},[1087,4309,4310],{"class":1089,"line":1403},[1087,4311,1803],{"emptyLinePlaceholder":1802},[1087,4313,4314,4317,4320,4322,4325,4327,4330,4332,4335,4337,4340,4342,4345],{"class":1089,"line":1425},[1087,4315,4316],{"class":1093},"def",[1087,4318,4319],{"class":1110}," verify_kaab_signature",[1087,4321,2657],{"class":1119},[1087,4323,4324],{"class":3035},"header",[1087,4326,1124],{"class":1123},[1087,4328,4329],{"class":3035}," raw_body",[1087,4331,1124],{"class":1123},[1087,4333,4334],{"class":3035}," secret",[1087,4336,1124],{"class":1123},[1087,4338,4339],{"class":3035}," now",[1087,4341,1162],{"class":1093},[1087,4343,4344],{"class":1097},"None",[1087,4346,3022],{"class":1119},[1087,4348,4349],{"class":1089,"line":1437},[1087,4350,4351],{"class":1170},"    \"\"\"True when `header` (the Kaab-Signature header) signs `raw_body` under `secret`.\n",[1087,4353,4354],{"class":1089,"line":1449},[1087,4355,1803],{"emptyLinePlaceholder":1802},[1087,4357,4358],{"class":1089,"line":2810},[1087,4359,4360],{"class":1170},"    `raw_body` is the request body exactly as it arrived, as bytes or str. The header is\n",[1087,4362,4363],{"class":1089,"line":2838},[1087,4364,4365],{"class":1170},"    `t=\u003Cunix seconds>,v1=\u003Chex>`, with a second `v1` for a day after a secret rotation; the\n",[1087,4367,4368],{"class":1089,"line":2854},[1087,4369,4370],{"class":1170},"    delivery is genuine when any `v1` is the HMAC-SHA256 of \"\u003Ct>.\u003Craw body>\".\n",[1087,4372,4373],{"class":1089,"line":2895},[1087,4374,4375],{"class":1170},"    \"\"\"\n",[1087,4377,4378,4380,4382,4384],{"class":1089,"line":2908},[1087,4379,3443],{"class":1093},[1087,4381,3249],{"class":1093},[1087,4383,3930],{"class":1119},[1087,4385,3105],{"class":1123},[1087,4387,4388,4391],{"class":1089,"line":2920},[1087,4389,4390],{"class":1093},"        return",[1087,4392,4393],{"class":1097}," False\n",[1087,4395,4396,4399,4401,4403,4405,4408,4410,4412],{"class":1089,"line":2955},[1087,4397,4398],{"class":1119},"    parts ",[1087,4400,1162],{"class":1093},[1087,4402,3930],{"class":1119},[1087,4404,1111],{"class":1123},[1087,4406,4407],{"class":1110},"split",[1087,4409,2657],{"class":1123},[1087,4411,3938],{"class":1136},[1087,4413,1223],{"class":1123},[1087,4415,4416,4418,4420,4423,4425,4428,4431,4434,4436,4438],{"class":1089,"line":2986},[1087,4417,3443],{"class":1093},[1087,4419,3249],{"class":1093},[1087,4421,4422],{"class":1119}," parts",[1087,4424,1210],{"class":1123},[1087,4426,4427],{"class":1097},"0",[1087,4429,4430],{"class":1123},"].",[1087,4432,4433],{"class":1110},"startswith",[1087,4435,2657],{"class":1123},[1087,4437,3966],{"class":1136},[1087,4439,3022],{"class":1123},[1087,4441,4442,4444],{"class":1089,"line":2992},[1087,4443,4390],{"class":1093},[1087,4445,4393],{"class":1097},[1087,4447,4448,4450],{"class":1089,"line":2998},[1087,4449,2706],{"class":1093},[1087,4451,3105],{"class":1123},[1087,4453,4454,4457,4459,4462,4465,4467,4470,4472],{"class":1089,"line":3658},[1087,4455,4456],{"class":1119},"        timestamp ",[1087,4458,1162],{"class":1093},[1087,4460,4461],{"class":1097}," int",[1087,4463,4464],{"class":1123},"(parts[",[1087,4466,4427],{"class":1097},[1087,4468,4469],{"class":1123},"][",[1087,4471,3999],{"class":1097},[1087,4473,4474],{"class":1123},":])\n",[1087,4476,4477,4480,4483],{"class":1089,"line":3663},[1087,4478,4479],{"class":1093},"    except",[1087,4481,4482],{"class":1097}," ValueError",[1087,4484,3105],{"class":1123},[1087,4486,4487,4489],{"class":1089,"line":4029},[1087,4488,4390],{"class":1093},[1087,4490,4393],{"class":1097},[1087,4492,4493,4496,4498,4500,4503,4506,4509,4512,4515,4518,4520,4523],{"class":1089,"line":4064},[1087,4494,4495],{"class":1119},"    now ",[1087,4497,1162],{"class":1093},[1087,4499,4461],{"class":1097},[1087,4501,4502],{"class":1123},"(time.",[1087,4504,4505],{"class":1110},"time",[1087,4507,4508],{"class":1123},"())",[1087,4510,4511],{"class":1093}," if",[1087,4513,4514],{"class":1119}," now ",[1087,4516,4517],{"class":1093},"is",[1087,4519,3044],{"class":1097},[1087,4521,4522],{"class":1093}," else",[1087,4524,4525],{"class":1119}," now\n",[1087,4527,4528,4530,4533,4536,4538,4541,4544,4546],{"class":1089,"line":4069},[1087,4529,3443],{"class":1093},[1087,4531,4532],{"class":1110}," abs",[1087,4534,4535],{"class":1123},"(now ",[1087,4537,4045],{"class":1093},[1087,4539,4540],{"class":1123}," timestamp)",[1087,4542,4543],{"class":1093}," >",[1087,4545,3731],{"class":1119},[1087,4547,3105],{"class":1123},[1087,4549,4550,4552],{"class":1089,"line":4124},[1087,4551,4390],{"class":1093},[1087,4553,4393],{"class":1097},[1087,4555,4556],{"class":1089,"line":4143},[1087,4557,1803],{"emptyLinePlaceholder":1802},[1087,4559,4560,4563,4565,4567,4569,4572,4574,4577,4579,4581,4584,4587,4590,4592,4594],{"class":1089,"line":4170},[1087,4561,4562],{"class":1119},"    body ",[1087,4564,1162],{"class":1093},[1087,4566,4329],{"class":1119},[1087,4568,1111],{"class":1123},[1087,4570,4571],{"class":1110},"encode",[1087,4573,2657],{"class":1123},[1087,4575,4576],{"class":1136},"\"utf-8\"",[1087,4578,2798],{"class":1123},[1087,4580,4511],{"class":1093},[1087,4582,4583],{"class":1110}," isinstance",[1087,4585,4586],{"class":1123},"(raw_body, ",[1087,4588,4589],{"class":1097},"str",[1087,4591,2798],{"class":1123},[1087,4593,4522],{"class":1093},[1087,4595,4596],{"class":1119}," raw_body\n",[1087,4598,4599,4602,4604,4607,4610,4612,4614,4617,4619,4622,4625,4628,4630],{"class":1089,"line":4206},[1087,4600,4601],{"class":1119},"    signed ",[1087,4603,1162],{"class":1093},[1087,4605,4606],{"class":1097}," str",[1087,4608,4609],{"class":1123},"(timestamp).",[1087,4611,4571],{"class":1110},[1087,4613,2657],{"class":1123},[1087,4615,4616],{"class":1136},"\"ascii\"",[1087,4618,2798],{"class":1123},[1087,4620,4621],{"class":1093}," +",[1087,4623,4624],{"class":1093}," b",[1087,4626,4627],{"class":1136},"\".\"",[1087,4629,4621],{"class":1093},[1087,4631,4632],{"class":1119}," body\n",[1087,4634,4635,4638,4640,4643,4645,4648,4651,4653,4655,4657,4660,4663],{"class":1089,"line":4242},[1087,4636,4637],{"class":1119},"    expected ",[1087,4639,1162],{"class":1093},[1087,4641,4642],{"class":1119}," hmac",[1087,4644,1111],{"class":1123},[1087,4646,4647],{"class":1110},"new",[1087,4649,4650],{"class":1123},"(secret.",[1087,4652,4571],{"class":1110},[1087,4654,2657],{"class":1123},[1087,4656,4576],{"class":1136},[1087,4658,4659],{"class":1123},"), signed, hashlib.sha256).",[1087,4661,4662],{"class":1110},"hexdigest",[1087,4664,1434],{"class":1123},[1087,4666,4667,4669,4672],{"class":1089,"line":4247},[1087,4668,4209],{"class":1093},[1087,4670,4671],{"class":1110}," any",[1087,4673,2735],{"class":1123},[1087,4675,4677,4680,4682,4684,4686,4688,4691,4694,4697,4700,4702,4705,4708,4711,4714,4717,4720],{"class":1089,"line":4676},34,[1087,4678,4679],{"class":1123},"        part.",[1087,4681,4433],{"class":1110},[1087,4683,2657],{"class":1123},[1087,4685,4159],{"class":1136},[1087,4687,2671],{"class":1123},[1087,4689,4690],{"class":1093},"and",[1087,4692,4693],{"class":1123}," hmac.",[1087,4695,4696],{"class":1110},"compare_digest",[1087,4698,4699],{"class":1123},"(part[",[1087,4701,4194],{"class":1097},[1087,4703,4704],{"class":1123},":], expected) ",[1087,4706,4707],{"class":1093},"for",[1087,4709,4710],{"class":1123}," part ",[1087,4712,4713],{"class":1093},"in",[1087,4715,4716],{"class":1123}," parts[",[1087,4718,4719],{"class":1097},"1",[1087,4721,4722],{"class":1123},":]\n",[1087,4724,4726],{"class":1089,"line":4725},35,[1087,4727,4728],{"class":1123},"    )\n",[1087,4730,4732],{"class":1089,"line":4731},36,[1087,4733,1803],{"emptyLinePlaceholder":1802},[1087,4735,4737],{"class":1089,"line":4736},37,[1087,4738,1803],{"emptyLinePlaceholder":1802},[1087,4740,4742,4744,4747,4750,4753],{"class":1089,"line":4741},38,[1087,4743,1326],{"class":1093},[1087,4745,4746],{"class":1097}," __name__",[1087,4748,4749],{"class":1093}," ==",[1087,4751,4752],{"class":1136}," \"__main__\"",[1087,4754,3105],{"class":1123},[1087,4756,4758],{"class":1089,"line":4757},39,[1087,4759,4760],{"class":1170},"    # Test harness: `{ \"secret\", \"signature\", \"body\", \"now\" }` as JSON on standard input.\n",[1087,4762,4764,4767,4769,4772,4774,4777],{"class":1089,"line":4763},40,[1087,4765,4766],{"class":1119},"    data ",[1087,4768,1162],{"class":1093},[1087,4770,4771],{"class":1119}," json",[1087,4773,1111],{"class":1123},[1087,4775,4776],{"class":1110},"load",[1087,4778,4779],{"class":1123},"(sys.stdin)\n",[1087,4781,4783,4786,4788,4790,4793,4796,4799,4802,4804,4807,4809,4812],{"class":1089,"line":4782},41,[1087,4784,4785],{"class":1119},"    ok ",[1087,4787,1162],{"class":1093},[1087,4789,4319],{"class":1110},[1087,4791,4792],{"class":1123},"(data[",[1087,4794,4795],{"class":1136},"\"signature\"",[1087,4797,4798],{"class":1123},"], data[",[1087,4800,4801],{"class":1136},"\"body\"",[1087,4803,4798],{"class":1123},[1087,4805,4806],{"class":1136},"\"secret\"",[1087,4808,4798],{"class":1123},[1087,4810,4811],{"class":1136},"\"now\"",[1087,4813,3275],{"class":1123},[1087,4815,4817,4820,4822,4825,4827,4830,4833,4836],{"class":1089,"line":4816},42,[1087,4818,4819],{"class":1110},"    print",[1087,4821,2657],{"class":1123},[1087,4823,4824],{"class":1136},"\"valid\"",[1087,4826,4511],{"class":1093},[1087,4828,4829],{"class":1123}," ok ",[1087,4831,4832],{"class":1093},"else",[1087,4834,4835],{"class":1136}," \"invalid\"",[1087,4837,1223],{"class":1123},[1077,4839,4841],{"className":1242,"code":4840,"filename":1244,"language":1245,"meta":1083,"style":1083},"import (\n    \"crypto\u002Fhmac\"\n    \"crypto\u002Fsha256\"\n    \"encoding\u002Fhex\"\n    \"strconv\"\n    \"strings\"\n    \"time\"\n)\n\n\u002F\u002F ToleranceSeconds is how far a delivery's timestamp may be from now.\nconst ToleranceSeconds = 300\n\n\u002F\u002F VerifyKaabSignature reports whether header (the Kaab-Signature request header) signs rawBody,\n\u002F\u002F the request body exactly as it arrived, under secret, the endpoint's whsec_… secret.\n\u002F\u002F\n\u002F\u002F The header is t=\u003Cunix seconds>,v1=\u003Chex>, with a second v1 for a day after a secret rotation;\n\u002F\u002F the delivery is genuine when any v1 is the HMAC-SHA256 of \"\u003Ct>.\u003CrawBody>\".\nfunc VerifyKaabSignature(header string, rawBody []byte, secret string, now time.Time) bool {\n    parts := strings.Split(header, \",\")\n    if len(parts) \u003C 2 || !strings.HasPrefix(parts[0], \"t=\") {\n        return false\n    }\n    timestamp, err := strconv.ParseInt(parts[0][2:], 10, 64)\n    if err != nil {\n        return false\n    }\n    age := now.Unix() - timestamp\n    if age > ToleranceSeconds || age \u003C -ToleranceSeconds {\n        return false\n    }\n\n    mac := hmac.New(sha256.New, []byte(secret))\n    mac.Write([]byte(strconv.FormatInt(timestamp, 10) + \".\"))\n    mac.Write(rawBody)\n    expected := mac.Sum(nil)\n    for _, part := range parts[1:] {\n        if !strings.HasPrefix(part, \"v1=\") {\n            continue\n        }\n        given, err := hex.DecodeString(part[3:])\n        if err == nil && hmac.Equal(given, expected) {\n            return true\n        }\n    }\n    return false\n}\n",[1040,4842,4843,4850,4855,4860,4865,4870,4875,4880,4884,4888,4893,4904,4908,4913,4918,4923,4928,4933,4973,4992,5029,5036,5040,5074,5086,5092,5096,5116,5141,5147,5151,5155,5175,5210,5219,5238,5258,5275,5280,5285,5304,5325,5332,5337,5342,5349],{"__ignoreMap":1083},[1087,4844,4845,4847],{"class":1089,"line":1090},[1087,4846,3695],{"class":1093},[1087,4848,4849],{"class":1119}," (\n",[1087,4851,4852],{"class":1089,"line":1156},[1087,4853,4854],{"class":1136},"    \"crypto\u002Fhmac\"\n",[1087,4856,4857],{"class":1089,"line":1341},[1087,4858,4859],{"class":1136},"    \"crypto\u002Fsha256\"\n",[1087,4861,4862],{"class":1089,"line":1353},[1087,4863,4864],{"class":1136},"    \"encoding\u002Fhex\"\n",[1087,4866,4867],{"class":1089,"line":1359},[1087,4868,4869],{"class":1136},"    \"strconv\"\n",[1087,4871,4872],{"class":1089,"line":1376},[1087,4873,4874],{"class":1136},"    \"strings\"\n",[1087,4876,4877],{"class":1089,"line":1389},[1087,4878,4879],{"class":1136},"    \"time\"\n",[1087,4881,4882],{"class":1089,"line":1398},[1087,4883,1223],{"class":1119},[1087,4885,4886],{"class":1089,"line":1403},[1087,4887,1803],{"emptyLinePlaceholder":1802},[1087,4889,4890],{"class":1089,"line":1425},[1087,4891,4892],{"class":1170},"\u002F\u002F ToleranceSeconds is how far a delivery's timestamp may be from now.\n",[1087,4894,4895,4897,4900,4902],{"class":1089,"line":1437},[1087,4896,1094],{"class":1093},[1087,4898,4899],{"class":1097}," ToleranceSeconds",[1087,4901,1101],{"class":1093},[1087,4903,4303],{"class":1097},[1087,4905,4906],{"class":1089,"line":1449},[1087,4907,1803],{"emptyLinePlaceholder":1802},[1087,4909,4910],{"class":1089,"line":2810},[1087,4911,4912],{"class":1170},"\u002F\u002F VerifyKaabSignature reports whether header (the Kaab-Signature request header) signs rawBody,\n",[1087,4914,4915],{"class":1089,"line":2838},[1087,4916,4917],{"class":1170},"\u002F\u002F the request body exactly as it arrived, under secret, the endpoint's whsec_… secret.\n",[1087,4919,4920],{"class":1089,"line":2854},[1087,4921,4922],{"class":1170},"\u002F\u002F\n",[1087,4924,4925],{"class":1089,"line":2895},[1087,4926,4927],{"class":1170},"\u002F\u002F The header is t=\u003Cunix seconds>,v1=\u003Chex>, with a second v1 for a day after a secret rotation;\n",[1087,4929,4930],{"class":1089,"line":2908},[1087,4931,4932],{"class":1170},"\u002F\u002F the delivery is genuine when any v1 is the HMAC-SHA256 of \"\u003Ct>.\u003CrawBody>\".\n",[1087,4934,4935,4937,4940,4943,4945,4948,4951,4954,4956,4959,4961,4963,4966,4968,4971],{"class":1089,"line":2920},[1087,4936,3394],{"class":1093},[1087,4938,4939],{"class":1110}," VerifyKaabSignature",[1087,4941,4942],{"class":1119},"(header ",[1087,4944,1282],{"class":1093},[1087,4946,4947],{"class":1119},", rawBody []",[1087,4949,4950],{"class":1093},"byte",[1087,4952,4953],{"class":1119},", secret ",[1087,4955,1282],{"class":1093},[1087,4957,4958],{"class":1119},", now ",[1087,4960,4505],{"class":1110},[1087,4962,1111],{"class":1119},[1087,4964,4965],{"class":1110},"Time",[1087,4967,2671],{"class":1119},[1087,4969,4970],{"class":1093},"bool",[1087,4972,1338],{"class":1119},[1087,4974,4975,4977,4979,4982,4985,4988,4990],{"class":1089,"line":2955},[1087,4976,4398],{"class":1119},[1087,4978,1255],{"class":1093},[1087,4980,4981],{"class":1119}," strings.",[1087,4983,4984],{"class":1110},"Split",[1087,4986,4987],{"class":1119},"(header, ",[1087,4989,3938],{"class":1136},[1087,4991,1223],{"class":1119},[1087,4993,4994,4996,4999,5002,5005,5008,5010,5012,5015,5018,5020,5022,5025,5027],{"class":1089,"line":2986},[1087,4995,3443],{"class":1093},[1087,4997,4998],{"class":1110}," len",[1087,5000,5001],{"class":1119},"(parts) ",[1087,5003,5004],{"class":1093},"\u003C",[1087,5006,5007],{"class":1097}," 2",[1087,5009,3954],{"class":1093},[1087,5011,3608],{"class":1093},[1087,5013,5014],{"class":1119},"strings.",[1087,5016,5017],{"class":1110},"HasPrefix",[1087,5019,4464],{"class":1119},[1087,5021,4427],{"class":1097},[1087,5023,5024],{"class":1119},"], ",[1087,5026,3966],{"class":1136},[1087,5028,3419],{"class":1119},[1087,5030,5031,5033],{"class":1089,"line":2992},[1087,5032,4390],{"class":1093},[1087,5034,5035],{"class":1097}," false\n",[1087,5037,5038],{"class":1089,"line":2998},[1087,5039,2989],{"class":1119},[1087,5041,5042,5045,5047,5050,5053,5055,5057,5059,5061,5064,5067,5069,5072],{"class":1089,"line":3658},[1087,5043,5044],{"class":1119},"    timestamp, err ",[1087,5046,1255],{"class":1093},[1087,5048,5049],{"class":1119}," strconv.",[1087,5051,5052],{"class":1110},"ParseInt",[1087,5054,4464],{"class":1119},[1087,5056,4427],{"class":1097},[1087,5058,4469],{"class":1119},[1087,5060,3999],{"class":1097},[1087,5062,5063],{"class":1119},":], ",[1087,5065,5066],{"class":1097},"10",[1087,5068,1547],{"class":1119},[1087,5070,5071],{"class":1097},"64",[1087,5073,1223],{"class":1119},[1087,5075,5076,5078,5080,5082,5084],{"class":1089,"line":3663},[1087,5077,3443],{"class":1093},[1087,5079,1329],{"class":1119},[1087,5081,1332],{"class":1093},[1087,5083,1335],{"class":1097},[1087,5085,1338],{"class":1119},[1087,5087,5088,5090],{"class":1089,"line":4029},[1087,5089,4390],{"class":1093},[1087,5091,5035],{"class":1097},[1087,5093,5094],{"class":1089,"line":4064},[1087,5095,2989],{"class":1119},[1087,5097,5098,5101,5103,5106,5109,5111,5113],{"class":1089,"line":4069},[1087,5099,5100],{"class":1119},"    age ",[1087,5102,1255],{"class":1093},[1087,5104,5105],{"class":1119}," now.",[1087,5107,5108],{"class":1110},"Unix",[1087,5110,3863],{"class":1119},[1087,5112,4045],{"class":1093},[1087,5114,5115],{"class":1119}," timestamp\n",[1087,5117,5118,5120,5123,5125,5128,5131,5133,5135,5138],{"class":1089,"line":4124},[1087,5119,3443],{"class":1093},[1087,5121,5122],{"class":1119}," age ",[1087,5124,4051],{"class":1093},[1087,5126,5127],{"class":1119}," ToleranceSeconds ",[1087,5129,5130],{"class":1093},"||",[1087,5132,5122],{"class":1119},[1087,5134,5004],{"class":1093},[1087,5136,5137],{"class":1093}," -",[1087,5139,5140],{"class":1119},"ToleranceSeconds {\n",[1087,5142,5143,5145],{"class":1089,"line":4143},[1087,5144,4390],{"class":1093},[1087,5146,5035],{"class":1097},[1087,5148,5149],{"class":1089,"line":4170},[1087,5150,2989],{"class":1119},[1087,5152,5153],{"class":1089,"line":4206},[1087,5154,1803],{"emptyLinePlaceholder":1802},[1087,5156,5157,5160,5162,5164,5167,5170,5172],{"class":1089,"line":4242},[1087,5158,5159],{"class":1119},"    mac ",[1087,5161,1255],{"class":1093},[1087,5163,4693],{"class":1119},[1087,5165,5166],{"class":1110},"New",[1087,5168,5169],{"class":1119},"(sha256.New, []",[1087,5171,4950],{"class":1093},[1087,5173,5174],{"class":1119},"(secret))\n",[1087,5176,5177,5180,5183,5186,5188,5191,5194,5197,5199,5201,5204,5207],{"class":1089,"line":4247},[1087,5178,5179],{"class":1119},"    mac.",[1087,5181,5182],{"class":1110},"Write",[1087,5184,5185],{"class":1119},"([]",[1087,5187,4950],{"class":1093},[1087,5189,5190],{"class":1119},"(strconv.",[1087,5192,5193],{"class":1110},"FormatInt",[1087,5195,5196],{"class":1119},"(timestamp, ",[1087,5198,5066],{"class":1097},[1087,5200,2671],{"class":1119},[1087,5202,5203],{"class":1093},"+",[1087,5205,5206],{"class":1136}," \".\"",[1087,5208,5209],{"class":1119},"))\n",[1087,5211,5212,5214,5216],{"class":1089,"line":4676},[1087,5213,5179],{"class":1119},[1087,5215,5182],{"class":1110},[1087,5217,5218],{"class":1119},"(rawBody)\n",[1087,5220,5221,5223,5225,5228,5231,5233,5236],{"class":1089,"line":4725},[1087,5222,4637],{"class":1119},[1087,5224,1255],{"class":1093},[1087,5226,5227],{"class":1119}," mac.",[1087,5229,5230],{"class":1110},"Sum",[1087,5232,2657],{"class":1119},[1087,5234,5235],{"class":1097},"nil",[1087,5237,1223],{"class":1119},[1087,5239,5240,5243,5246,5248,5251,5253,5255],{"class":1089,"line":4731},[1087,5241,5242],{"class":1093},"    for",[1087,5244,5245],{"class":1119}," _, part ",[1087,5247,1255],{"class":1093},[1087,5249,5250],{"class":1093}," range",[1087,5252,4716],{"class":1119},[1087,5254,4719],{"class":1097},[1087,5256,5257],{"class":1119},":] {\n",[1087,5259,5260,5262,5264,5266,5268,5271,5273],{"class":1089,"line":4736},[1087,5261,3238],{"class":1093},[1087,5263,3608],{"class":1093},[1087,5265,5014],{"class":1119},[1087,5267,5017],{"class":1110},[1087,5269,5270],{"class":1119},"(part, ",[1087,5272,4159],{"class":1136},[1087,5274,3419],{"class":1119},[1087,5276,5277],{"class":1089,"line":4741},[1087,5278,5279],{"class":1093},"            continue\n",[1087,5281,5282],{"class":1089,"line":4757},[1087,5283,5284],{"class":1119},"        }\n",[1087,5286,5287,5290,5292,5295,5298,5300,5302],{"class":1089,"line":4763},[1087,5288,5289],{"class":1119},"        given, err ",[1087,5291,1255],{"class":1093},[1087,5293,5294],{"class":1119}," hex.",[1087,5296,5297],{"class":1110},"DecodeString",[1087,5299,4699],{"class":1119},[1087,5301,4194],{"class":1097},[1087,5303,4474],{"class":1119},[1087,5305,5306,5308,5310,5313,5315,5317,5319,5322],{"class":1089,"line":4782},[1087,5307,3238],{"class":1093},[1087,5309,1329],{"class":1119},[1087,5311,5312],{"class":1093},"==",[1087,5314,1335],{"class":1097},[1087,5316,4228],{"class":1093},[1087,5318,4693],{"class":1119},[1087,5320,5321],{"class":1110},"Equal",[1087,5323,5324],{"class":1119},"(given, expected) {\n",[1087,5326,5327,5330],{"class":1089,"line":4816},[1087,5328,5329],{"class":1093},"            return",[1087,5331,3621],{"class":1097},[1087,5333,5335],{"class":1089,"line":5334},43,[1087,5336,5284],{"class":1119},[1087,5338,5340],{"class":1089,"line":5339},44,[1087,5341,2989],{"class":1119},[1087,5343,5345,5347],{"class":1089,"line":5344},45,[1087,5346,4209],{"class":1093},[1087,5348,5035],{"class":1097},[1087,5350,5352],{"class":1089,"line":5351},46,[1087,5353,1356],{"class":1119},[1029,5355,5357],{"id":5356},"retries-ordering-and-failures","Retries, ordering and failures",[1025,5359,5360,5363,5364,5367,5368,5371,5372,5375],{},[1468,5361,5362],{},"Success"," is any ",[1040,5365,5366],{},"2xx"," answer within ",[1468,5369,5370],{},"10 seconds",". Answer first and do the work afterwards; a\nreceiver that takes longer is counted as failed and gets the event again. Redirects are not\nfollowed: a ",[1040,5373,5374],{},"3xx"," is a failure.",[1025,5377,5378,5381,5382,5385,5386,5389,5390,5393,5394,5396,5397,1509],{},[1468,5379,5380],{},"Retries."," A failed delivery is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours and\n12 hours (six attempts over about 14½ hours) and is then marked ",[1040,5383,5384],{},"exhausted",". A delivery whose\ndestination is refused is marked ",[1040,5387,5388],{},"failed"," at once and not retried. Every attempt, its status\ncode, its latency and the first kilobyte of your answer are in\n",[1040,5391,5392],{},"GET \u002Fv1\u002Fwebhooks\u002F{id}\u002Fdeliveries"," (filter with ",[1040,5395,2250],{}," and ",[1040,5398,5399],{},"event_type",[1025,5401,5402,5405,5406,5409,5410,5414,5415,5417],{},[1468,5403,5404],{},"Ordering."," Kaabist delivers to each endpoint ",[1468,5407,5408],{},"in the order the events happened, one at a time.","\nA delivery that is failing therefore holds back the later events for ",[5411,5412,5413],"em",{},"that endpoint"," until it\nsucceeds or exhausts its retries; other endpoints are not affected, however long that endpoint's\nbacklog grows. An event whose announcement was lost and is delivered late joins the endpoint's line\nbehind the delivery already at its head. Order across endpoints, and between an endpoint and a\njob's ",[1040,5416,1619],{},", is not guaranteed.",[1025,5419,5420,2568,5423,5426,5427,5429],{},[1468,5421,5422],{},"Resending.",[1040,5424,5425],{},"POST \u002Fv1\u002Fwebhooks\u002F{id}\u002Fdeliveries\u002F{delivery_id}\u002Fresend"," sends the same event again\nas a new delivery; the original record is left as it was. The event's ",[1040,5428,1924],{}," and body are the same,\nso a receiver that deduplicates treats it as a repeat.",[1025,5431,5432,5435,5436,5439,5440,5442,5443,5446,5447,5450,5451,5453,5454,5457,5458,5461],{},[1468,5433,5434],{},"Auto-disable."," An endpoint that has failed ",[1468,5437,5438],{},"100 deliveries in a row, over at least 24 hours,","\nis switched off: its ",[1040,5441,2250],{}," becomes ",[1040,5444,5445],{},"disabled"," with ",[1040,5448,5449],{},"disabled_reason: \"auto\"",", a\n",[1040,5452,2490],{}," event is recorded, and your organisation's owners and admins get an e-mail.\nEvents keep being recorded while it is off. Fix the receiver, ",[1040,5455,5456],{},"PATCH"," the endpoint back to\n",[1040,5459,5460],{},"\"status\": \"active\""," and resend what it missed.",[1029,5463,5465],{"id":5464},"receiving-safely","Receiving safely",[2576,5467,5468,5479,5488,5498],{},[2579,5469,5470,5475,5476,5478],{},[1468,5471,5472,5473,1111],{},"Deduplicate on ",[1040,5474,1924],{}," Kaabist delivers at least once: a delivery that timed out on our side may\nhave been processed on yours, and it will come again. Keep the ids you have handled (a unique\ncolumn is enough) and answer ",[1040,5477,5366],{}," to a repeat without doing the work twice.",[2579,5480,5481,5484,5485,5487],{},[1468,5482,5483],{},"Treat the body as a pointer."," Read the job, proposal or program from ",[1040,5486,1057],{}," with your API\nkey before acting on it; the API is the source of truth, and it is where the data lives.",[2579,5489,5490,5493,5494,5497],{},[1468,5491,5492],{},"Keep the secret out of source control",", and rotate it (",[1040,5495,5496],{},"POST \u002Fv1\u002Fwebhooks\u002F{id}\u002Frotate-secret",")\nif it leaks. The new secret is shown once; the old one keeps signing for a day.",[2579,5499,5500,2037,5503,5505],{},[1468,5501,5502],{},"Use separate endpoints for test and live",[1040,5504,1541],{},") if your receivers are separate.",[1029,5507,5509],{"id":5508},"testing-locally","Testing locally",[1025,5511,5512,5514],{},[1040,5513,1606],{}," answers with the delivery record (status, latency, the start of\nyour answer), so you can see a misconfigured receiver without waiting for a real event.",[1025,5516,5517,5518,5521,5522,5524,5525,5527,5528,5531,5532,1111],{},"To receive real deliveries on a laptop, run a receiver locally and expose it through a tunnel, for\nexample ",[1040,5519,5520],{},"cloudflared tunnel --url http:\u002F\u002Flocalhost:4400",", then register the tunnel's ",[1040,5523,1502],{},"\naddress as the endpoint's ",[1040,5526,610],{},". The example receivers listen on port 4400 by default\n(",[1040,5529,5530],{},"KAAB_WEBHOOK_PORT",") and read the secret from ",[1040,5533,5534],{},"KAAB_WEBHOOK_SECRET",[1029,5536,5538],{"id":5537},"where-deliveries-may-go","Where deliveries may go",[1025,5540,5541,5542,2749,5545,1547,5548,1547,5551,5554,5555,5396,5558,5561,5562,5565,5566,5446,5568,5571,5572,5575],{},"Kaabist resolves an endpoint's hostname before every delivery and connects only to the address it\nchecked. Every address the name resolves to must be public: private ranges (",[1040,5543,5544],{},"10\u002F8",[1040,5546,5547],{},"172.16\u002F12",[1040,5549,5550],{},"192.168\u002F16",[1040,5552,5553],{},"100.64\u002F10","), loopback, link-local, cloud metadata addresses, their\nIPv6 equivalents, ",[1040,5556,5557],{},".internal",[1040,5559,5560],{},".local"," names and numeric spellings like ",[1040,5563,5564],{},"2130706433"," are\nrefused. A delivery refused this way is recorded as ",[1040,5567,5388],{},[1040,5569,5570],{},"error: \"ssrf_blocked\""," and is\nnot retried. URLs with credentials in them (",[1040,5573,5574],{},"https:\u002F\u002Fuser:pass@…",") are refused when the endpoint is\nsaved.",[1029,5577,5579],{"id":5578},"worked-example","Worked example",[1025,5581,5582],{},"Receive a job's events from start to finish:",[5584,5585,5586,5596,5610,5673,5696],"ol",{},[2579,5587,5588,5589,5592,5593,5595],{},"Register an endpoint for ",[1040,5590,5591],{},"[\"job.*\"]"," and keep the ",[1040,5594,1462],{}," from the response.",[2579,5597,5598,5600,5601,3679,5604,5607,5608,1111],{},[1040,5599,1606],{},". The answer is a delivery record with ",[1040,5602,5603],{},"event_type: \"ping\"",[1040,5605,5606],{},"status: \"succeeded\"",", and your receiver logs a verified ",[1040,5609,1610],{},[2579,5611,5612,5613,1130,5616],{},"Run a job against a product page with ",[1040,5614,5615],{},"Prefer: wait=30",[1077,5617,5619],{"className":1664,"code":5618,"language":680,"meta":1083,"style":1083},"{ \"url\": \"https:\u002F\u002Fshop.example.com\u002Fp\u002F1\", \"extract\": { \"title\": \"h1\" }, \"options\": { \"max_tier\": \"http\" } }\n",[1040,5620,5621],{"__ignoreMap":1083},[1087,5622,5623,5625,5627,5629,5632,5634,5637,5639,5642,5645,5647,5650,5653,5655,5658,5660,5662,5665,5667,5670],{"class":1089,"line":1090},[1087,5624,1672],{"class":1119},[1087,5626,1292],{"class":1093},[1087,5628,1130],{"class":1123},[1087,5630,5631],{"class":1136}," \"https:\u002F\u002Fshop.example.com\u002Fp\u002F1\"",[1087,5633,1124],{"class":1123},[1087,5635,5636],{"class":1093}," \"extract\"",[1087,5638,1130],{"class":1123},[1087,5640,5641],{"class":1119}," { ",[1087,5643,5644],{"class":1093},"\"title\"",[1087,5646,1130],{"class":1123},[1087,5648,5649],{"class":1136}," \"h1\"",[1087,5651,5652],{"class":1119}," }",[1087,5654,1124],{"class":1123},[1087,5656,5657],{"class":1093}," \"options\"",[1087,5659,1130],{"class":1123},[1087,5661,5641],{"class":1119},[1087,5663,5664],{"class":1093},"\"max_tier\"",[1087,5666,1130],{"class":1123},[1087,5668,5669],{"class":1136}," \"http\"",[1087,5671,5672],{"class":1119}," } }\n",[2579,5674,5675,5676,1547,5678,2749,5680,5682,5683,5686,5687,5689,5690,5692,5693,1111],{},"Your receiver gets three deliveries for that job, in this order: ",[1040,5677,2006],{},[1040,5679,2050],{},[1040,5681,1521],{},". Each carries ",[1040,5684,5685],{},"User-Agent: Kaab-Webhooks\u002F1",", a ",[1040,5688,1792],{}," that verifies\nwith your secret (and fails with any other), and ",[1040,5691,1057],{}," pointing at\n",[1040,5694,5695],{},"https:\u002F\u002Fapi.kaab.ist\u002Fv1\u002Fjobs\u002F\u003Cjob id>",[2579,5697,5698,5701],{},[1040,5699,5700],{},"GET \u002Fv1\u002Fwebhooks\u002F{id}\u002Fdeliveries?status=succeeded"," lists the same deliveries with their status\ncodes and latencies.",[1029,5703,5705],{"id":5704},"pitfalls","Pitfalls",[2576,5707,5708,5714,5723,5731,5739,5745],{},[2579,5709,5710,5713],{},[1468,5711,5712],{},"Parsing before verifying."," A framework that parses JSON before your handler runs hands you a\nre-serialized body, and the signature no longer matches. Read the raw bytes first.",[2579,5715,5716,5719,5720,5722],{},[1468,5717,5718],{},"Doing the work before answering."," Anything that pushes the answer past 10 seconds counts as a\nfailure and brings the event back. Answer ",[1040,5721,5366],{},", then process.",[2579,5724,5725,5728,5729,1111],{},[1468,5726,5727],{},"Assuming exactly once."," Repeats happen, by design. Deduplicate on the event ",[1040,5730,1924],{},[2579,5732,5733,5736,5737,1111],{},[1468,5734,5735],{},"Assuming the payload holds the data."," It does not. Read the resource from ",[1040,5738,1057],{},[2579,5740,5741,5744],{},[1468,5742,5743],{},"One broken endpoint for everything."," A failing delivery holds back that endpoint's later\nevents. Separate endpoints for separate consumers keep one outage from delaying the others.",[2579,5746,5747,5750],{},[1468,5748,5749],{},"Clocks."," The five-minute window assumes your server's clock is right; keep it synchronised.",[1029,5752,155],{"id":5753},"reference",[2576,5755,5756,5779,5792,5802],{},[2579,5757,5758,5759,2749,5763,2749,5767,2749,5771,2749,5775],{},"API: ",[1505,5760,5762],{"href":5761},"\u002Freference\u002Fapi\u002Fwebhooks\u002Fcreate-webhook","create a webhook",[1505,5764,5766],{"href":5765},"\u002Freference\u002Fapi\u002Fwebhooks\u002Ftest-webhook","send a test",[1505,5768,5770],{"href":5769},"\u002Freference\u002Fapi\u002Fwebhooks\u002Flist-webhook-deliveries","list deliveries",[1505,5772,5774],{"href":5773},"\u002Freference\u002Fapi\u002Fwebhooks\u002Fresend-webhook-delivery","resend a delivery",[1505,5776,5778],{"href":5777},"\u002Freference\u002Fapi\u002Fwebhooks\u002Frotate-webhook-secret","rotate the secret",[2579,5780,5781,5782,1547,5785,2749,5789],{},"Schemas: ",[1505,5783,513],{"href":5784},"\u002Freference\u002Fapi\u002Fschemas\u002Fwebhook",[1505,5786,5788],{"href":5787},"\u002Freference\u002Fapi\u002Fschemas\u002Fwebhook-delivery","delivery",[1505,5790,2826],{"href":5791},"\u002Freference\u002Fapi\u002Fschemas\u002Fwebhook-event",[2579,5793,5794,1547,5798],{},[1505,5795,5797],{"href":5796},"\u002Freference\u002Fevents","Every event and its data",[1505,5799,5801],{"href":5800},"\u002Freference\u002Fcodes#events","event types",[2579,5803,5804,5805],{},"CLI: ",[1505,5806,5808],{"href":5807},"\u002Freference\u002Fcli\u002Fkaab-webhooks-create",[1040,5809,995],{},[5811,5812,5813],"style",{},"html pre.shiki code .sqn18, html code.shiki .sqn18{--shiki-light:var(--kaab-code-token-keyword);--shiki-default:var(--kaab-code-token-keyword);--shiki-dark:var(--kaab-code-token-keyword)}html pre.shiki code .sHJIl, html code.shiki .sHJIl{--shiki-light:var(--kaab-code-token-constant);--shiki-default:var(--kaab-code-token-constant);--shiki-dark:var(--kaab-code-token-constant)}html pre.shiki code .sJugJ, html code.shiki .sJugJ{--shiki-light:var(--kaab-code-token-function);--shiki-default:var(--kaab-code-token-function);--shiki-dark:var(--kaab-code-token-function)}html pre.shiki code .sTzsn, html code.shiki .sTzsn{--shiki-light:var(--kaab-code-foreground);--shiki-default:var(--kaab-code-foreground);--shiki-dark:var(--kaab-code-foreground)}html pre.shiki code .sQm2-, html code.shiki .sQm2-{--shiki-light:var(--kaab-code-token-punctuation);--shiki-default:var(--kaab-code-token-punctuation);--shiki-dark:var(--kaab-code-token-punctuation)}html pre.shiki code .suOy4, html code.shiki .suOy4{--shiki-light:var(--kaab-code-token-string-expression);--shiki-default:var(--kaab-code-token-string-expression);--shiki-dark:var(--kaab-code-token-string-expression)}html pre.shiki code .sKog0, html code.shiki .sKog0{--shiki-light:var(--kaab-code-token-comment);--shiki-default:var(--kaab-code-token-comment);--shiki-dark:var(--kaab-code-token-comment)}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 .sx8Bj, html code.shiki .sx8Bj{--shiki-light:var(--kaab-code-token-string);--shiki-default:var(--kaab-code-token-string);--shiki-dark:var(--kaab-code-token-string)}html pre.shiki code .sIH4l, html code.shiki .sIH4l{--shiki-light:var(--kaab-code-token-parameter);--shiki-default:var(--kaab-code-token-parameter);--shiki-dark:var(--kaab-code-token-parameter)}",{"title":1083,"searchDepth":1341,"depth":1341,"links":5815},[5816,5817,5822,5823,5824,5827,5828,5829,5830,5831,5832,5833],{"id":1031,"depth":1156,"text":1032},{"id":1061,"depth":1156,"text":1062,"children":5818},[5819,5821],{"id":1615,"depth":1341,"text":5820},"A job's own webhook_url",{"id":1650,"depth":1341,"text":1651},{"id":1721,"depth":1156,"text":1722},{"id":839,"depth":1156,"text":838},{"id":2546,"depth":1156,"text":2547,"children":5825},[5826],{"id":3668,"depth":1341,"text":3669},{"id":5356,"depth":1156,"text":5357},{"id":5464,"depth":1156,"text":5465},{"id":5508,"depth":1156,"text":5509},{"id":5537,"depth":1156,"text":5538},{"id":5578,"depth":1156,"text":5579},{"id":5704,"depth":1156,"text":5705},{"id":5753,"depth":1156,"text":155},false,null,"How Kaabist tells your systems that something happened: the events, the signature, retries, ordering and how to receive them safely.","md",{},"\u002Fguides\u002Fwebhooks",{"title":104,"description":5836},"3.guides\u002F06.webhooks","V5DSilKo9u-WTyywRWRnDFsP-ld5yOnY5B4wnjvUd9I",[5844,5848],{"title":101,"path":5845,"stem":5846,"description":5847,"children":-1},"\u002Fguides\u002Fschedules-and-change-detection","3.guides\u002F05.schedules-and-change-detection","Run a program on a cron in your timezone, compare each result with the last one, and get a webhook only when a watched field changes.",{"title":107,"path":5849,"stem":5850,"description":5851,"children":-1},"\u002Fguides\u002Fbyo-providers","3.guides\u002F07.byo-providers","Use your own proxies, language models and captcha solvers: configuration, fallback, billing, health checks and how credentials are kept.",1793593417086]