[{"data":1,"prerenderedAt":654},["ShallowReactive",2],{"guide-/guides/building-an-ai-monitoring-app":3},{"id":4,"title":5,"body":6,"category":645,"description":646,"extension":647,"meta":648,"navigation":167,"order":128,"path":649,"readingTime":650,"seo":651,"stem":652,"__hash__":653},"guides/guides/building-an-ai-monitoring-app.md","Building an AI monitoring app",{"type":7,"value":8,"toc":636},"minimal",[9,13,17,22,56,60,77,80,100,111,115,482,489,493,539,543,591,594,598,605,609,629,632],[10,11,5],"h1",{"id":12},"building-an-ai-monitoring-app",[14,15,16],"p",{},"This guide walks you through the shape of an AI-verification app on CHeKT: a third-party service that listens to alarm events, fetches snapshots, runs them through a vision model, and writes back a verdict.",[18,19,21],"h2",{"id":20},"the-shape-of-the-integration","The shape of the integration",[23,24,25,38,44,50],"ul",{},[26,27,28,32,33],"li",{},[29,30,31],"strong",{},"Pattern:"," ",[34,35,37],"a",{"href":36},"/apps","CHeKT Apps",[26,39,40,43],{},[29,41,42],{},"Auth:"," API key",[26,45,46,49],{},[29,47,48],{},"Direction:"," AI app → CHeKT (your service calls our API)",[26,51,52,55],{},[29,53,54],{},"Time to first response:"," 45 minutes for a working prototype.",[18,57,59],{"id":58},"_1-create-the-app","1. Create the app",[14,61,62,63,67,68,67,71,67,73,76],{},"Sign in to ",[64,65,66],"code",{},"dealer.chekt.com"," → ",[29,69,70],{},"Settings",[29,72,37],{},[29,74,75],{},"New app",".",[14,78,79],{},"Permissions you need:",[23,81,82,88,94],{},[26,83,84,87],{},[64,85,86],{},"events:read"," — to confirm alarm context",[26,89,90,93],{},[64,91,92],{},"snapshots:read"," — to fetch images for analysis",[26,95,96,99],{},[64,97,98],{},"alarms:write"," — to acknowledge or annotate",[14,101,102,103,106,107,110],{},"Subscribe to the ",[64,104,105],{},"alarm.created"," and ",[64,108,109],{},"snapshot.created"," events.",[18,112,114],{"id":113},"_2-stand-up-a-webhook-receiver","2. Stand up a webhook receiver",[116,117,122],"pre",{"className":118,"code":119,"language":120,"meta":121,"style":121},"language-ts shiki shiki-themes github-light github-dark","import express from \"express\";\nimport crypto from \"crypto\";\n\nconst app = express();\napp.use(express.json({ verify: (req, _res, buf) => (req.rawBody = buf) }));\n\napp.post(\"/webhooks/chekt\", (req, res) => {\n  const sig = req.headers[\"x-chekt-signature\"] as string;\n  const expected = crypto\n    .createHmac(\"sha256\", process.env.CHEKT_WEBHOOK_SECRET!)\n    .update(req.rawBody)\n    .digest(\"hex\");\n\n  if (sig !== expected) return res.status(401).end();\n\n  if (req.body.type === \"alarm.created\") {\n    queue.publish(\"analyze\", req.body.data);\n  }\n  res.sendStatus(200);\n});\n","ts","",[64,123,124,147,162,169,189,243,248,279,307,320,346,357,373,378,415,420,437,454,460,476],{"__ignoreMap":121},[125,126,129,133,137,140,144],"span",{"class":127,"line":128},"line",1,[125,130,132],{"class":131},"szBVR","import",[125,134,136],{"class":135},"sVt8B"," express ",[125,138,139],{"class":131},"from",[125,141,143],{"class":142},"sZZnC"," \"express\"",[125,145,146],{"class":135},";\n",[125,148,150,152,155,157,160],{"class":127,"line":149},2,[125,151,132],{"class":131},[125,153,154],{"class":135}," crypto ",[125,156,139],{"class":131},[125,158,159],{"class":142}," \"crypto\"",[125,161,146],{"class":135},[125,163,165],{"class":127,"line":164},3,[125,166,168],{"emptyLinePlaceholder":167},true,"\n",[125,170,172,175,179,182,186],{"class":127,"line":171},4,[125,173,174],{"class":131},"const",[125,176,178],{"class":177},"sj4cs"," app",[125,180,181],{"class":131}," =",[125,183,185],{"class":184},"sScJk"," express",[125,187,188],{"class":135},"();\n",[125,190,192,195,198,201,204,207,210,213,217,220,223,225,228,231,234,237,240],{"class":127,"line":191},5,[125,193,194],{"class":135},"app.",[125,196,197],{"class":184},"use",[125,199,200],{"class":135},"(express.",[125,202,203],{"class":184},"json",[125,205,206],{"class":135},"({ ",[125,208,209],{"class":184},"verify",[125,211,212],{"class":135},": (",[125,214,216],{"class":215},"s4XuR","req",[125,218,219],{"class":135},", ",[125,221,222],{"class":215},"_res",[125,224,219],{"class":135},[125,226,227],{"class":215},"buf",[125,229,230],{"class":135},") ",[125,232,233],{"class":131},"=>",[125,235,236],{"class":135}," (req.rawBody ",[125,238,239],{"class":131},"=",[125,241,242],{"class":135}," buf) }));\n",[125,244,246],{"class":127,"line":245},6,[125,247,168],{"emptyLinePlaceholder":167},[125,249,251,253,256,259,262,265,267,269,272,274,276],{"class":127,"line":250},7,[125,252,194],{"class":135},[125,254,255],{"class":184},"post",[125,257,258],{"class":135},"(",[125,260,261],{"class":142},"\"/webhooks/chekt\"",[125,263,264],{"class":135},", (",[125,266,216],{"class":215},[125,268,219],{"class":135},[125,270,271],{"class":215},"res",[125,273,230],{"class":135},[125,275,233],{"class":131},[125,277,278],{"class":135}," {\n",[125,280,282,285,288,290,293,296,299,302,305],{"class":127,"line":281},8,[125,283,284],{"class":131},"  const",[125,286,287],{"class":177}," sig",[125,289,181],{"class":131},[125,291,292],{"class":135}," req.headers[",[125,294,295],{"class":142},"\"x-chekt-signature\"",[125,297,298],{"class":135},"] ",[125,300,301],{"class":131},"as",[125,303,304],{"class":177}," string",[125,306,146],{"class":135},[125,308,310,312,315,317],{"class":127,"line":309},9,[125,311,284],{"class":131},[125,313,314],{"class":177}," expected",[125,316,181],{"class":131},[125,318,319],{"class":135}," crypto\n",[125,321,323,326,329,331,334,337,340,343],{"class":127,"line":322},10,[125,324,325],{"class":135},"    .",[125,327,328],{"class":184},"createHmac",[125,330,258],{"class":135},[125,332,333],{"class":142},"\"sha256\"",[125,335,336],{"class":135},", process.env.",[125,338,339],{"class":177},"CHEKT_WEBHOOK_SECRET",[125,341,342],{"class":131},"!",[125,344,345],{"class":135},")\n",[125,347,349,351,354],{"class":127,"line":348},11,[125,350,325],{"class":135},[125,352,353],{"class":184},"update",[125,355,356],{"class":135},"(req.rawBody)\n",[125,358,360,362,365,367,370],{"class":127,"line":359},12,[125,361,325],{"class":135},[125,363,364],{"class":184},"digest",[125,366,258],{"class":135},[125,368,369],{"class":142},"\"hex\"",[125,371,372],{"class":135},");\n",[125,374,376],{"class":127,"line":375},13,[125,377,168],{"emptyLinePlaceholder":167},[125,379,381,384,387,390,393,396,399,402,404,407,410,413],{"class":127,"line":380},14,[125,382,383],{"class":131},"  if",[125,385,386],{"class":135}," (sig ",[125,388,389],{"class":131},"!==",[125,391,392],{"class":135}," expected) ",[125,394,395],{"class":131},"return",[125,397,398],{"class":135}," res.",[125,400,401],{"class":184},"status",[125,403,258],{"class":135},[125,405,406],{"class":177},"401",[125,408,409],{"class":135},").",[125,411,412],{"class":184},"end",[125,414,188],{"class":135},[125,416,418],{"class":127,"line":417},15,[125,419,168],{"emptyLinePlaceholder":167},[125,421,423,425,428,431,434],{"class":127,"line":422},16,[125,424,383],{"class":131},[125,426,427],{"class":135}," (req.body.type ",[125,429,430],{"class":131},"===",[125,432,433],{"class":142}," \"alarm.created\"",[125,435,436],{"class":135},") {\n",[125,438,440,443,446,448,451],{"class":127,"line":439},17,[125,441,442],{"class":135},"    queue.",[125,444,445],{"class":184},"publish",[125,447,258],{"class":135},[125,449,450],{"class":142},"\"analyze\"",[125,452,453],{"class":135},", req.body.data);\n",[125,455,457],{"class":127,"line":456},18,[125,458,459],{"class":135},"  }\n",[125,461,463,466,469,471,474],{"class":127,"line":462},19,[125,464,465],{"class":135},"  res.",[125,467,468],{"class":184},"sendStatus",[125,470,258],{"class":135},[125,472,473],{"class":177},"200",[125,475,372],{"class":135},[125,477,479],{"class":127,"line":478},20,[125,480,481],{"class":135},"});\n",[14,483,484,485,488],{},"The 200 is the ",[29,486,487],{},"only"," thing CHeKT cares about. Do real work in a queue.",[18,490,492],{"id":491},"_3-fetch-the-snapshot","3. Fetch the snapshot",[116,494,496],{"className":118,"code":495,"language":120,"meta":121,"style":121},"const snap = await chekt.snapshots.get(alarm.snapshot_id);\nconst verdict = await vision.classify(snap.url);\n",[64,497,498,519],{"__ignoreMap":121},[125,499,500,502,505,507,510,513,516],{"class":127,"line":128},[125,501,174],{"class":131},[125,503,504],{"class":177}," snap",[125,506,181],{"class":131},[125,508,509],{"class":131}," await",[125,511,512],{"class":135}," chekt.snapshots.",[125,514,515],{"class":184},"get",[125,517,518],{"class":135},"(alarm.snapshot_id);\n",[125,520,521,523,526,528,530,533,536],{"class":127,"line":149},[125,522,174],{"class":131},[125,524,525],{"class":177}," verdict",[125,527,181],{"class":131},[125,529,509],{"class":131},[125,531,532],{"class":135}," vision.",[125,534,535],{"class":184},"classify",[125,537,538],{"class":135},"(snap.url);\n",[18,540,542],{"id":541},"_4-write-the-verdict-back","4. Write the verdict back",[116,544,546],{"className":118,"code":545,"language":120,"meta":121,"style":121},"await chekt.alarms.annotate(alarm.id, {\n  source: \"ai-verifier\",\n  verdict: verdict.label,        // \"intruder\" | \"false-positive\" | \"uncertain\"\n  confidence: verdict.confidence,\n});\n",[64,547,548,562,573,582,587],{"__ignoreMap":121},[125,549,550,553,556,559],{"class":127,"line":128},[125,551,552],{"class":131},"await",[125,554,555],{"class":135}," chekt.alarms.",[125,557,558],{"class":184},"annotate",[125,560,561],{"class":135},"(alarm.id, {\n",[125,563,564,567,570],{"class":127,"line":149},[125,565,566],{"class":135},"  source: ",[125,568,569],{"class":142},"\"ai-verifier\"",[125,571,572],{"class":135},",\n",[125,574,575,578],{"class":127,"line":164},[125,576,577],{"class":135},"  verdict: verdict.label,        ",[125,579,581],{"class":580},"sJ8bj","// \"intruder\" | \"false-positive\" | \"uncertain\"\n",[125,583,584],{"class":127,"line":171},[125,585,586],{"class":135},"  confidence: verdict.confidence,\n",[125,588,589],{"class":127,"line":191},[125,590,481],{"class":135},[14,592,593],{},"A central station operator now sees the AI annotation alongside the alarm in real time.",[18,595,597],{"id":596},"_5-handle-retries-safely","5. Handle retries safely",[14,599,600,601,76],{},"Use idempotency keys on every write. CHeKT retries webhooks with exponential backoff for 24 hours — your side might receive duplicates. See ",[34,602,604],{"href":603},"/docs/idempotency","Idempotency",[18,606,608],{"id":607},"what-to-ship-next","What to ship next",[23,610,611,617,623],{},[26,612,613,616],{},[29,614,615],{},"Acknowledge low-confidence false positives"," — reduce operator load.",[26,618,619,622],{},[29,620,621],{},"Dispatch on high-confidence intruder events"," — auto-escalate.",[26,624,625,628],{},[29,626,627],{},"Surface metrics in your dashboard"," — AI catch rate, false-positive rate.",[14,630,631],{},"That's it — a real AI integration in a single afternoon.",[633,634,635],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":121,"searchDepth":164,"depth":164,"links":637},[638,639,640,641,642,643,644],{"id":20,"depth":149,"text":21},{"id":58,"depth":149,"text":59},{"id":113,"depth":149,"text":114},{"id":491,"depth":149,"text":492},{"id":541,"depth":149,"text":542},{"id":596,"depth":149,"text":597},{"id":607,"depth":149,"text":608},"Integration","How to ship a Public-API-driven AI integration in a weekend.","md",{},"/guides/building-an-ai-monitoring-app","12 min",{"title":5,"description":646},"guides/building-an-ai-monitoring-app","_mkwX7ZZEYLv-_WCiqKj6BVCfmJARjLrBCjNBOHSYsM",1779905486278]