<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: InstaChime</title>
    <description>The latest articles on DEV Community by InstaChime (@instachime).</description>
    <link>https://dev.to/instachime</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3969099%2Fc1af7127-d760-448b-8545-8d07966c2eb2.png</url>
      <title>DEV Community: InstaChime</title>
      <link>https://dev.to/instachime</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/instachime"/>
    <language>en</language>
    <item>
      <title>Routing Autonomous Browser-Agent Handshakes (Stagehand &amp; Playwright) to Human Closers Before Context Drift</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Mon, 26 Oct 2026 01:49:07 +0000</pubDate>
      <link>https://dev.to/instachime/routing-autonomous-browser-agent-handshakes-stagehand-playwright-to-human-closers-before-20g0</link>
      <guid>https://dev.to/instachime/routing-autonomous-browser-agent-handshakes-stagehand-playwright-to-human-closers-before-20g0</guid>
      <description>&lt;p&gt;Autonomous browser agents have changed how teams handle web data extraction, form filling, and back-office workflows on portals that have no API. Stagehand, from Browserbase, combines deterministic browser control with LLM reasoning through four primitives: &lt;code&gt;act()&lt;/code&gt;, &lt;code&gt;extract()&lt;/code&gt;, &lt;code&gt;observe()&lt;/code&gt;, and &lt;code&gt;agent()&lt;/code&gt;. Playwright remains one of the most common drivers underneath this kind of automation, and Stagehand can operate on Playwright &lt;code&gt;Page&lt;/code&gt; objects directly.&lt;/p&gt;

&lt;p&gt;These agents still stall. MFA prompts, CAPTCHAs that slip past automated solving, unexpected layout changes, and approval steps that policy says a person must own all stop an agent. What happens in the next few minutes decides whether a human can finish the job or whether you restart from scratch.&lt;/p&gt;

&lt;p&gt;This article shows how to build a handoff pipeline that detects a stall, preserves the live browser session, posts an interactive Slack claim card, lets exactly one human take over, and then resumes the agent with a clean context instead of a polluted one.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What “Context Drift” Means for Browser Agents
&lt;/h2&gt;

&lt;p&gt;“Context drift” is a practical label, not a formal research term. It describes what happens when a stalled agent keeps going. Its internal picture of the task diverges from what the browser is actually showing, and its context fills with failed attempts.&lt;/p&gt;

&lt;p&gt;Two things drive it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Longer context makes models less reliable.&lt;/strong&gt; Chroma’s 2025 “Context Rot” report tested 18 frontier models and found that performance becomes increasingly unreliable as input length grows, even on simple tasks. Each failed step an agent keeps in its history adds to the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The page keeps changing while the agent reasons.&lt;/strong&gt; Session-timeout overlays, asynchronous modals, and redirects can invalidate the agent’s last observation before it acts on it.&lt;/p&gt;

&lt;p&gt;Stagehand’s own design reflects this. Its documentation recommends breaking work into single-step &lt;code&gt;act()&lt;/code&gt; calls and using &lt;code&gt;observe()&lt;/code&gt; to preview an action before executing it. The &lt;code&gt;agent()&lt;/code&gt; method defaults to a &lt;code&gt;maxSteps&lt;/code&gt; limit of 20, and &lt;code&gt;AgentResult&lt;/code&gt; reports &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;completed&lt;/code&gt;, and token &lt;code&gt;usage&lt;/code&gt;, so you can measure a run instead of guessing.&lt;/p&gt;

&lt;p&gt;The conclusion is that retrying forever is the wrong response to a stall. Detect the stall quickly, preserve the session, hand it to a human, and restart the agent with a short, fresh instruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Where Stagehand, Playwright, and Browserbase Fit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stagehand v3&lt;/strong&gt; is a standalone library that no longer depends on Playwright. It can still drive Playwright, Puppeteer, or Patchright pages by passing a &lt;code&gt;page&lt;/code&gt; option to &lt;code&gt;act()&lt;/code&gt;, &lt;code&gt;extract()&lt;/code&gt;, and &lt;code&gt;observe()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright&lt;/strong&gt; gives you reliable screenshot and DOM tooling. This article uses it to capture the stall screenshot. Playwright 1.56 also introduced “Playwright Agents” (planner, generator, healer), but those are for authoring and repairing tests, not for runtime browsing. They are a different thing from the runtime agents discussed here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browserbase&lt;/strong&gt; hosts the cloud browser. Its Live View lets a person watch and control a running session. Browserbase documents this as a human-in-the-loop feature, including handing credentials entry to the end user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Defining Interception Triggers
&lt;/h2&gt;

&lt;p&gt;Don’t rely on the model to announce its own failure. Use signals your harness can measure.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Step budget exhausted or &lt;code&gt;success: false&lt;/code&gt;.&lt;/strong&gt; Run &lt;code&gt;agent.execute()&lt;/code&gt; with an explicit &lt;code&gt;maxSteps&lt;/code&gt; and treat an unsuccessful result as a handoff candidate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wall-clock timeout.&lt;/strong&gt; Non-CUA agents (DOM and hybrid modes) accept an &lt;code&gt;AbortSignal&lt;/code&gt;, which lets you cancel a run that has exceeded its time budget. This is an experimental feature that requires &lt;code&gt;experimental: true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No-progress heuristic.&lt;/strong&gt; The &lt;code&gt;onStepFinish&lt;/code&gt; callback fires after each step. If the page URL hasn’t changed across many consecutive steps, treat the agent as stuck. Tune the threshold to your workflow, because form-heavy pages legitimately take many steps on one URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Known blocker signatures.&lt;/strong&gt; Challenge pages, login redirects, 403 responses, and MFA screens can be matched deterministically and should skip any further agent attempts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy gates.&lt;/strong&gt; Payments above a limit, KYC sign-offs, and other compliance checkpoints should always route to a human by design, regardless of how well the agent is doing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stagehand’s &lt;code&gt;observe()&lt;/code&gt; returns candidate actions with a selector, description, method, and arguments. It does not return a numeric confidence score, so don’t build a handoff rule on a &lt;code&gt;confidence &amp;lt; 0.75&lt;/code&gt; threshold. Use the observable signals above.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Stagehand agent on Browserbase session (keepAlive)]
        │  stall detected (abort / failure / policy gate)
        ▼
[Capture screenshot via Playwright (mask sensitive fields)]
        ▼
[Backend dispatcher: store task + session ID, post Slack card]
        ▼
[Slack claim card] ── human clicks "Claim" ──► [atomic lock (Redis SET NX)]
        ▼
[Ephemeral message to the claimer with the Live View link]
        ▼
[Human resolves blocker → "Return to agent" or "Complete"]
        ▼
[Agent resumes with a fresh, short instruction]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keep the session alive, deliberately
&lt;/h3&gt;

&lt;p&gt;By default, a Browserbase session ends when the client disconnects or its timeout is reached. To hold a session for a human, you need two settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;keepAlive: true&lt;/code&gt; lets the session survive disconnects. Availability depends on your Browserbase plan.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;timeout&lt;/code&gt; long enough to cover your claim window plus the human’s work. You can set it per session or as a project default. The maximum session duration is 6 hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;code&gt;keepAlive&lt;/code&gt; enabled, Stagehand’s &lt;code&gt;close()&lt;/code&gt; no longer terminates the browser. Release the session explicitly when you finish, or it keeps running and billing.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Implementation
&lt;/h2&gt;

&lt;p&gt;The sketch below uses a single process with an in-memory waiter map to stay readable. In production, replace the map with a queue or Redis pub/sub so the Slack handler and the agent worker can run on different machines. Check the current SDK reference for exact option and type names, because Stagehand and the Browserbase SDK evolve quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The agent runner with stall detection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Stagehand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@browserbasehq/stagehand&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Browserbase&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@browserbasehq/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@slack/web-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Browserbase&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BROWSERBASE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resume&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abort&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waiters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Outcome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signalHandoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Outcome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;)?.(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runAgentOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stagehand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Stagehand&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;instruction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallClock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wall-clock budget exceeded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lastUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;samePageSteps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;instruction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;maxSteps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;onStepFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="nx"&gt;samePageSteps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;lastUrl&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;samePageSteps&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;lastUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;samePageSteps&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no page progress for 8 steps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`agent reported incomplete: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AgentAbortError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wallClock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runWithHandoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stagehand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Stagehand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BROWSERBASE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BROWSERBASE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BROWSERBASE_PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// required for abort signals and callbacks&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anthropic/claude-sonnet-4-6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;browserbaseSessionCreateParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keepAlive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;browserbaseSessionID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;instruction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stallReason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runAgentOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;instruction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;stallReason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// finished without help&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;requestHandoff&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stallReason&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resume&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Handoff ended: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Fresh context: no stale message history, just a compact summary.&lt;/span&gt;
      &lt;span class="nx"&gt;instruction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="s2"&gt;`A human operator resolved a blocker on the current page (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stallReason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;). `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="s2"&gt;`Re-inspect the page and continue this task: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BROWSERBASE_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REQUEST_RELEASE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Capturing the screenshot and posting the claim card
&lt;/h3&gt;

&lt;p&gt;Slack’s &lt;code&gt;image&lt;/code&gt; block needs either a publicly hosted &lt;code&gt;image_url&lt;/code&gt; or a &lt;code&gt;slack_file&lt;/code&gt; reference, so an internal CDN URL won’t render. The simplest approach is to upload the screenshot to the card’s thread with &lt;code&gt;filesUploadV2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Playwright’s &lt;code&gt;screenshot()&lt;/code&gt; accepts a &lt;code&gt;mask&lt;/code&gt; option, which lets you cover sensitive fields before the image ever leaves your infrastructure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CLAIM_TTL_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;requestHandoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stagehand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;stallReason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Outcome&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stallReason&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Attach Playwright to the same cloud browser to take the screenshot.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectOverCDP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stagehand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectURL&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pwPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;png&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pwPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fullPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pwPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[type="password"], [data-sensitive]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pwPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// disconnects; the session survives because keepAlive is on&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`handoff:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stallReason&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_TRIAGE_CHANNEL_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Browser agent needs a human&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Browser agent needs a human&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Task:*\n&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Page:*\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Reason:*\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stallReason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;actions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claim_browser_session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Claim &amp;amp; take over&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;danger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abort_browser_session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Abort task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filesUploadV2&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;thread_ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;png&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;initial_comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Screenshot at the moment of the stall (sensitive fields masked).&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Outcome&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Expire the handoff if nobody claims it in time.&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`handoff:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:owner`&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;CLAIM_TTL_MS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling the claim: atomic lock, then a private takeover link
&lt;/h3&gt;

&lt;p&gt;Slack requires an HTTP 200 acknowledgment within 3 seconds of receiving an interaction payload, so call &lt;code&gt;ack()&lt;/code&gt; first and do the work afterward. The &lt;code&gt;response_url&lt;/code&gt; in the payload can be used up to five times within 30 minutes.&lt;/p&gt;

&lt;p&gt;The Live View URL grants control of a running browser. Treat it like a credential: send it only to the person who claimed the task, in an ephemeral message, and never post it in the shared channel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BlockAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ButtonAction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@slack/bolt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;signingSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_SIGNING_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BlockAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claim_browser_session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;ButtonAction&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Only the first click wins. SET ... NX is atomic in Redis.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;won&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`handoff:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:owner`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;won&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postEphemeral&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Someone else already claimed this session.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`handoff:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sessionId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;debuggerFullscreenUrl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Public card: show who owns it, with no sensitive link.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Claimed by &amp;lt;@&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Claimed by &amp;lt;@&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;* — task &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;actions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return_to_agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Return control to agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete_task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Task complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Private message: only the claimer sees the control link.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postEphemeral&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Open the live session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;actions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open_live_view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Open live session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;debuggerFullscreenUrl&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]}],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BlockAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return_to_agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;signalHandoff&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;ButtonAction&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resume&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BlockAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete_task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;signalHandoff&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;ButtonAction&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BlockAction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abort_browser_session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;signalHandoff&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;ButtonAction&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abort&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two Slack details are worth knowing. A button with a &lt;code&gt;url&lt;/code&gt; still sends your app an interaction payload, which you must acknowledge. Also, “Return control” and “Task complete” are only meaningful for the person who claimed the task, so in production verify that &lt;code&gt;body.user.id&lt;/code&gt; matches the stored owner before signaling.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Resuming Without Dragging Context Along
&lt;/h2&gt;

&lt;p&gt;The loop in &lt;code&gt;runWithHandoff&lt;/code&gt; does not reuse the previous run’s message history. It starts a new &lt;code&gt;agent.execute()&lt;/code&gt; with a short instruction that says what the human resolved and tells the agent to re-inspect the page. Stagehand does offer message continuation through the &lt;code&gt;messages&lt;/code&gt; field, but for a stalled run that history is the polluted context you are trying to leave behind. Reserve continuation for healthy multi-step tasks.&lt;/p&gt;

&lt;p&gt;If you want the agent to avoid repeating work, pass the state it needs as structured data (for example, which form pages are already complete) rather than as the raw transcript.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Best Practices for Production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set a claim TTL and a cleanup path.&lt;/strong&gt; If no one claims the card within your window (10 minutes in the example, which is a design choice rather than a platform rule), alert an escalation channel and release the session. Because &lt;code&gt;keepAlive&lt;/code&gt; sessions keep running until released or timed out, always set an explicit &lt;code&gt;timeout&lt;/code&gt; and call &lt;code&gt;REQUEST_RELEASE&lt;/code&gt; in a &lt;code&gt;finally&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mask sensitive data before anything leaves your systems.&lt;/strong&gt; Use Playwright’s &lt;code&gt;mask&lt;/code&gt; option for known fields. Also keep credentials out of the model’s view: Stagehand’s &lt;code&gt;variables&lt;/code&gt; feature substitutes values at runtime so the LLM sees only the placeholder names. Set &lt;code&gt;verbose: 0&lt;/code&gt; to keep secrets out of logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify ownership on every callback.&lt;/strong&gt; Check the Slack request signature (Bolt does this for you) and confirm that the user pressing “Return control” is the user holding the lock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalate intentionally for policy gates.&lt;/strong&gt; For payments and compliance steps, route to a human before the agent acts, not after it fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instrument handoffs.&lt;/strong&gt; Record the trigger type (step budget, wall-clock timeout, no-progress, blocker signature, policy gate), time to claim, time to resolve, and whether the agent succeeded after resuming. Use your own numbers to decide which failures to fix in prompts, which to turn into deterministic rules, and which to leave to humans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respect the sites you automate.&lt;/strong&gt; Make sure your automation and any human takeover comply with the target portal’s terms and your organization’s authorization requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Putting It Together
&lt;/h2&gt;

&lt;p&gt;A reliable handoff comes down to four decisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect stalls with measurable circuit breakers (step budget, abort signal, progress heuristics, deterministic blockers) instead of trusting the model’s self-assessment.&lt;/li&gt;
&lt;li&gt;Preserve the browser session on purpose, using &lt;code&gt;keepAlive&lt;/code&gt;, a sensible &lt;code&gt;timeout&lt;/code&gt;, and explicit release.&lt;/li&gt;
&lt;li&gt;Give humans a safe, race-free way in: a Slack claim card, an atomic lock, a private Live View link, and a masked screenshot.&lt;/li&gt;
&lt;li&gt;Resume with a clean, compact instruction rather than the transcript of a failing run.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Stagehand &lt;code&gt;agent()&lt;/code&gt; and &lt;code&gt;act()&lt;/code&gt; documentation: &lt;a href="https://docs.stagehand.dev/v3/basics/agent" rel="noopener noreferrer"&gt;https://docs.stagehand.dev/v3/basics/agent&lt;/a&gt; and &lt;a href="https://docs.stagehand.dev/v3/basics/act" rel="noopener noreferrer"&gt;https://docs.stagehand.dev/v3/basics/act&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browserbase Live View: &lt;a href="https://docs.browserbase.com/platform/browser/observability/session-live-view" rel="noopener noreferrer"&gt;https://docs.browserbase.com/platform/browser/observability/session-live-view&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browserbase session timeouts and keep alive: &lt;a href="https://docs.browserbase.com/platform/browser/long-sessions/overview" rel="noopener noreferrer"&gt;https://docs.browserbase.com/platform/browser/long-sessions/overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Slack interaction handling (3-second acknowledgment, &lt;code&gt;response_url&lt;/code&gt;): &lt;a href="https://docs.slack.dev/interactivity/handling-user-interaction" rel="noopener noreferrer"&gt;https://docs.slack.dev/interactivity/handling-user-interaction&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Slack Block Kit blocks reference: &lt;a href="https://api.slack.com/reference/block-kit/blocks" rel="noopener noreferrer"&gt;https://api.slack.com/reference/block-kit/blocks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Chroma, “Context Rot: How Increasing Input Tokens Impacts LLM Performance”: &lt;a href="https://research.trychroma.com/context-rot" rel="noopener noreferrer"&gt;https://research.trychroma.com/context-rot&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/routing-autonomous-browser-agent-handshakes-stagehand-playwright-to-human-closers-before-context-drift" rel="noopener noreferrer"&gt;https://instachime.com/blog/routing-autonomous-browser-agent-handshakes-stagehand-playwright-to-human-closers-before-context-drift&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
    </item>
    <item>
      <title>Routing Browser-Agent Workflows vs. Human SDR Handoffs: The New Speed-to-Lead Frontier</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Sun, 25 Oct 2026 04:10:56 +0000</pubDate>
      <link>https://dev.to/instachime/routing-browser-agent-workflows-vs-human-sdr-handoffs-the-new-speed-to-lead-frontier-3h69</link>
      <guid>https://dev.to/instachime/routing-browser-agent-workflows-vs-human-sdr-handoffs-the-new-speed-to-lead-frontier-3h69</guid>
      <description>&lt;p&gt;Speed-to-lead has long been a numbers game. The most-cited research is the MIT/InsideSales.com Lead Response Management Study, led by Dr. James Oldroyd. It analyzed more than 15,000 leads and 100,000 call attempts, and found that contacting a lead within five minutes made a team about 100 times more likely to reach that lead, and 21 times more likely to qualify it, than waiting 30 minutes. That study dates from 2007, and its figures are usually repeated through vendor summaries. Treat them as directional evidence of how fast intent decays, not as a precise forecast for your funnel.&lt;/p&gt;

&lt;p&gt;Most teams still miss the five-minute window. A 2026 benchmark of B2B inbound leads, reported by Aloware, found a median first response of about 42 hours, with only about 7% of teams responding within five minutes. That gap is the opportunity.&lt;/p&gt;

&lt;p&gt;Browser-based AI agents are the newest tool for closing it. They can open a prospect’s website, read a gated page, or work through a portal that has no API, then hand a sales development representative (SDR) a prepared briefing. They also fail in predictable ways, such as CAPTCHAs, login challenges, ambiguous interfaces and high-stakes actions. The workable design is a hybrid. Agents handle research and enrichment, and a well-built escalation path puts a human in the live browser session the moment the agent gets stuck.&lt;/p&gt;

&lt;p&gt;A note on timing: a browser agent works in seconds to minutes per task, not milliseconds. The realistic win is cutting first-touch time from hours to minutes or less, not reaching zero latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. From API Pipelines to Computer-Using Agents
&lt;/h2&gt;

&lt;p&gt;Classic inbound routing connects web forms, enrichment providers (such as Clearbit or ZoomInfo) and a CRM (HubSpot, Salesforce) through APIs. It is fast and dependable where an API exists. It struggles when the data you need lives somewhere without one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sites without APIs:&lt;/strong&gt; business registries, niche industry directories, and older vendor portals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-step interactions:&lt;/strong&gt; information behind several clicks, filters or logins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual context:&lt;/strong&gt; what a prospect’s pricing page, checkout flow or product UI actually shows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Computer-using agents work in those gaps by operating a browser the way a person would.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where OpenAI Operator Stands
&lt;/h3&gt;

&lt;p&gt;OpenAI launched Operator on January 23, 2025 as a research preview for ChatGPT Pro users in the United States. It was powered by the Computer-Using Agent (CUA) model, which combines GPT-4o’s vision with reinforcement-learning-trained reasoning to interpret screenshots and use standard browser controls.&lt;/p&gt;

&lt;p&gt;Operator is no longer a standalone product. OpenAI folded its browser-control capabilities into ChatGPT agent on July 17, 2025, and the standalone Operator experience was deprecated on August 1, 2025, with access ending August 31, 2025.&lt;/p&gt;

&lt;p&gt;OpenAI’s agent products have kept changing since then. Secondary sources report that ChatGPT’s agent mode was itself reorganized in 2026, with browsing capabilities moving into other ChatGPT surfaces. Because this area changes quickly, check OpenAI’s current documentation before designing a workflow around any specific OpenAI agent product. Architecturally, treat “Operator-style” agents as a category: hosted, vision-driven agents that work from screenshots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser Use: The Open-Source Option
&lt;/h3&gt;

&lt;p&gt;Browser Use is an open-source Python library that connects LLMs to a real browser so an agent can complete tasks from a plain-language instruction. It is commonly paired with Playwright, and it works with remote browsers over the Chrome DevTools Protocol (CDP). The company also sells a hosted Browser Use Cloud, which offers agent tasks, cloud browsers (remote Chrome instances you control over CDP, with stealth, proxies, live preview and recording), and an option to point the open-source library at those cloud browsers. Its documentation says this setup includes stealth infrastructure, proxies and CAPTCHA solving.&lt;/p&gt;

&lt;p&gt;Running it yourself gives engineering teams more control over model choice, state handling, proxies and where data lives. The trade-off is that you operate the infrastructure and own the reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Inbound lead / signal]
          │
          ▼
[Event trigger → orchestrator]
          │
          ▼
[Browser agent: enrich, verify, brief]
          │
          ▼
[Risk &amp;amp; confidence checks]
     │             │
   (pass)       (fail / high stakes)
     ▼             ▼
[Autonomous    [Human SDR handoff
 follow-up]     with live session]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. When to Hand Off to a Human
&lt;/h2&gt;

&lt;p&gt;An orchestrator should check state after every meaningful agent step and decide whether to continue or escalate. Three categories cover most cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactive and Security Barriers
&lt;/h3&gt;

&lt;p&gt;CAPTCHAs, SMS or app-based two-factor authentication and one-time passcodes are designed to stop automation. Cloud browser vendors advertise CAPTCHA handling, but a human can solve these more reliably than a script. The agent should pause and alert someone rather than fail silently or retry in a loop. Be aware that automating past some bot defenses can violate a site’s terms of service, so involve legal or compliance before building workflows around it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Low Confidence or Ambiguous Interfaces
&lt;/h3&gt;

&lt;p&gt;Agents misread custom date pickers, nested iframes, and contradictory pricing tiers. Many frameworks don’t expose a calibrated confidence score, so you often have to build a proxy yourself: a second model call that grades the step, a schema validation failure, repeated retries, or a step budget being exceeded. The &lt;code&gt;0.85&lt;/code&gt; threshold used below is only an example. Tune any threshold against your own logged outcomes.&lt;/p&gt;

&lt;h3&gt;
  
  
  High-Value or Irreversible Actions
&lt;/h3&gt;

&lt;p&gt;Set hard limits on anything with financial or contractual consequences, such as submitting a procurement form, starting a paid trial or booking a paid onboarding. The dollar threshold is a business decision (the code below uses &lt;code&gt;$50,000&lt;/code&gt; as a placeholder). The principle matches how OpenAI describes ChatGPT agent, which asks for permission before taking actions of consequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Designing the Handoff Experience
&lt;/h2&gt;

&lt;p&gt;Speed-to-lead is lost if an SDR spends minutes reconstructing what the agent was doing. A good handoff includes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A live session link.&lt;/strong&gt; The SDR lands in the same remote browser the agent was using, not on a homepage. Cloud browser providers support this. Browserbase, for example, offers a Live View whose documentation describes human-in-the-loop use (“instantly take control or provide input”), embedding via iframe, and a separate debugger URL per tab. Cloudflare’s Browser Run added Live View, Human in the Loop and Session Recordings in April 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A short, structured briefing.&lt;/strong&gt; Include who the lead is, what the agent found, and exactly where and why it stopped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A one-click path to action.&lt;/strong&gt; Put the link in Slack or Teams, with the CRM record attached, so the SDR can take over the browser, approve the action or call the lead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep the briefing honest. Mark anything the agent inferred as inferred, and give the SDR the evidence (URL, screenshot, extracted text) rather than only a conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Implementation Sketch (TypeScript)
&lt;/h2&gt;

&lt;p&gt;The following is illustrative, not production-ready. It uses a cloud browser with a live-view URL, because a Playwright server’s &lt;code&gt;wsEndpoint&lt;/code&gt; is not something a human can open in a browser tab. &lt;code&gt;evaluateState&lt;/code&gt; is a hypothetical helper that calls a vision-capable model and returns structured flags. Verify the SDK calls against current Browserbase documentation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Browserbase&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@browserbasehq/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Browserbase&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BROWSERBASE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Lead&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;targetUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;valueEstimate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PageState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;hasCaptcha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;requiresMfa&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isCheckoutOrForm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// from your own grading step, not a vendor-provided score&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Hypothetical: sends a screenshot to a vision model and returns structured flags&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;evaluateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PageState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ACV_GATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// example business threshold&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_CONFIDENCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// example; tune against real outcomes&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processLead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Lead&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BROWSERBASE_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// keep the session alive long enough for a human takeover&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectOverCDP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;domcontentloaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;evaluateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasCaptcha&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requiresMfa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Security barrier (CAPTCHA/MFA)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MIN_CONFIDENCE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Low confidence (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueEstimate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ACV_GATE&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCheckoutOrForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`High-value lead ($&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueEstimate&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) at a form or checkout`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Autonomous path: enrichment only. Irreversible actions stay behind the gates above.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETED_AUTONOMOUSLY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extracted&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;`Exception: &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Lead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;live&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SDR needed: lead &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nReason: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nPage: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nLive session: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;live&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debuggerFullscreenUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nExtracted: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ROUTED_TO_HUMAN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Practical notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never expose raw browser endpoints.&lt;/strong&gt; Treat live-view and CDP URLs as credentials, scope them to the session, and expire them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t let the agent submit forms with invented data.&lt;/strong&gt; Any write action should be gated or limited to approved fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log everything.&lt;/strong&gt; Keep screenshots and step logs so you can audit failures and tune thresholds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Comparing the Approaches
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Hosted vision agent (Operator-style)&lt;/th&gt;
&lt;th&gt;Browser Use (open source)&lt;/th&gt;
&lt;th&gt;Human SDR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hosted model interprets screenshots and drives a browser&lt;/td&gt;
&lt;td&gt;Python library connecting LLMs to a browser, typically via Playwright/CDP&lt;/td&gt;
&lt;td&gt;Person operates the live session or phone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Strengths&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Little infrastructure; handles unfamiliar layouts&lt;/td&gt;
&lt;td&gt;Control over models, proxies, sessions and data location; can use cloud browsers&lt;/td&gt;
&lt;td&gt;Judgment, negotiation, security challenges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weaknesses&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Product availability has changed repeatedly; less control&lt;/td&gt;
&lt;td&gt;You own reliability and operations&lt;/td&gt;
&lt;td&gt;Slow if not alerted; limited hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Varies by task and model; measure it&lt;/td&gt;
&lt;td&gt;Varies by model, page and infrastructure; measure it&lt;/td&gt;
&lt;td&gt;Depends on alerting and staffing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ad hoc exploration of unfamiliar sites&lt;/td&gt;
&lt;td&gt;Repeatable enrichment and data extraction at scale&lt;/td&gt;
&lt;td&gt;Barriers, high-value or sensitive decisions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Published per-step latency numbers vary widely with model, page weight and network, so benchmark with your own targets instead of relying on vendor figures.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. A Three-Tier Blueprint
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tier 1: Autonomous enrichment.&lt;/strong&gt; When a lead arrives, an agent gathers public company information and verifies signals from the lead’s web presence. Keep it read-only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 2: Validation and routing.&lt;/strong&gt; Rules and model checks decide whether to proceed. Any security barrier, low-confidence step, or high-value action escalates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 3: Human takeover.&lt;/strong&gt; An SDR receives an alert with the live session, a brief and CRM context, and then calls, approves, or resumes the agent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The business metric to track is time to first meaningful human or automated touch. Measure it from form submission to first contact, split by whether the lead took the autonomous or escalated path.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Risks to Manage
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terms of service and legality.&lt;/strong&gt; Automated access to gated or logged-in sites can breach site terms. Review this with legal counsel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; Enriching personal data can trigger GDPR, CCPA or similar obligations. Collect only what you need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability.&lt;/strong&gt; Browser agents are probabilistic. Expect failures and measure success rates on your own workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security.&lt;/strong&gt; Live sessions can contain credentials and sensitive data. Restrict access and keep audit logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor churn.&lt;/strong&gt; Agent products and pricing are changing fast, as OpenAI’s own Operator-to-ChatGPT-agent transition shows. Keep the orchestration layer separate from any one provider so you can swap them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The five-minute benchmark has held up for nearly two decades, and most teams still miss it. Browser agents can close part of that gap by doing the research a rep would otherwise do, but they cannot clear every barrier on their own. The teams that benefit most will pair them with a fast, well-instrumented handoff, so that when an agent gets stuck, the right human is in the right browser tab within moments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/routing-browser-agent-workflows-vs-human-sdr-handoffs-the-new-speed-to-lead-frontier" rel="noopener noreferrer"&gt;https://instachime.com/blog/routing-browser-agent-workflows-vs-human-sdr-handoffs-the-new-speed-to-lead-frontier&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
    </item>
    <item>
      <title>Mastering Human-in-the-Loop (HITL) Escalations: Routing Make.com and Zapier Agentic Workflows to Slack Sales Teams</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Sat, 24 Oct 2026 01:45:53 +0000</pubDate>
      <link>https://dev.to/instachime/mastering-human-in-the-loop-hitl-escalations-routing-makecom-and-zapier-agentic-workflows-to-1c0h</link>
      <guid>https://dev.to/instachime/mastering-human-in-the-loop-hitl-escalations-routing-makecom-and-zapier-agentic-workflows-to-1c0h</guid>
      <description>&lt;p&gt;Revenue teams increasingly use agentic workflows and multi-step automation builders such as &lt;strong&gt;Make.com&lt;/strong&gt; and &lt;strong&gt;Zapier&lt;/strong&gt; to qualify inbound leads, enrich records and start prospect conversations. Even well-built agents hit edge cases. Examples include enterprise procurement questions, unusual budget figures, pricing negotiations and frustrated high-value prospects.&lt;/p&gt;

&lt;p&gt;A note on naming: Zapier Central, launched in 2024, was renamed Zapier Agents in early 2025. In October 2026 Zapier announced it is folding Agents into &lt;strong&gt;AI by Zapier&lt;/strong&gt;, an AI step you add to any Zap in the standard editor. This guide uses “Zapier” for the current product and mentions Central or Agents only for historical context. Where older tutorials describe the standalone Agents app, expect the interface to differ.&lt;/p&gt;

&lt;p&gt;This guide shows how to build escalation paths that hand work from an automated agent to a person in Slack, with a clear owner for each escalated lead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Human-in-the-Loop Matters in Sales Automation
&lt;/h2&gt;

&lt;p&gt;Automation handles repeatable work well. That includes parsing inbound emails, enriching company data, drafting first-touch outreach and logging CRM updates. For enrichment, check that your data provider still sells what you need. Clearbit, for example, is now part of HubSpot’s Breeze Intelligence, and new customers are generally directed to HubSpot rather than a standalone API. Apollo is another common alternative.&lt;/p&gt;

&lt;p&gt;Autonomous language-model steps also carry specific risks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Confident errors.&lt;/strong&gt; A model can state wrong facts about pricing or policy with total assurance. In &lt;em&gt;Moffatt v. Air Canada&lt;/em&gt; (2024), a British Columbia tribunal held the airline liable after its website chatbot gave a customer incorrect information about bereavement fares. The tribunal rejected the argument that the chatbot was responsible for its own statements. A sales agent that improvises discounts or contract terms creates the same kind of exposure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-value leads.&lt;/strong&gt; A large inbound opportunity usually deserves a senior account executive (AE), not a templated sequence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stalled conversations.&lt;/strong&gt; If an agent runs out of context or a tool call fails, the prospect shouldn’t be left waiting in silence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A HITL design lets the workflow recognize these cases, pause, package the relevant context and put it in front of a person in &lt;strong&gt;Slack&lt;/strong&gt;, where most sales teams already work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Speed Matters
&lt;/h2&gt;

&lt;p&gt;The most-cited research on lead response time is the Lead Response Management Study by James Oldroyd with InsideSales.com. It examined over 15,000 web-generated leads and more than 100,000 call attempts across six companies. It found that contacting a lead within five minutes, rather than 30, was associated with roughly 100 times higher odds of reaching the lead and 21 times higher odds of qualifying it.&lt;/p&gt;

&lt;p&gt;The study measured &lt;em&gt;qualification&lt;/em&gt;, not closed deals, so “21 times more likely to convert” overstates it. A separate 2011 Harvard Business Review analysis, “The Short Life of Online Sales Leads,” audited 2,241 US companies. It found that firms responding within an hour were nearly seven times more likely to qualify a lead than firms that waited one more hour. These studies concern human follow-up on web leads and predate today’s tooling. Treat them as strong evidence that speed matters, not as a guaranteed uplift for your funnel.&lt;/p&gt;

&lt;p&gt;For escalations, this means your workflow’s main job is to get a qualified human to a hot lead quickly. Track that time and set an internal target, with five minutes a reasonable starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of an Escalation Loop
&lt;/h2&gt;

&lt;p&gt;A dependable escalation design has four phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trigger and monitoring.&lt;/strong&gt; A scenario or Zap watches an inbound channel, a CRM event or an agent’s output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation.&lt;/strong&gt; Filters check deal size, intent, sentiment, an explicit request for a human, or a failed tool call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload assembly.&lt;/strong&gt; The workflow gathers the prospect’s details, a short summary, the lead score and a CRM link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack delivery and ownership.&lt;/strong&gt; A message with action buttons goes to the right channel, and the first valid click assigns an owner.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One caution on evaluation: the agent doesn’t produce a reliable confidence score on its own. If you ask a model to rate its own certainty, treat that number as a rough heuristic. Prefer deterministic signals where you can, such as an explicit pricing request, a deal size above a threshold, a known account name, or a tool error. Have the agent return structured fields (for example &lt;code&gt;intent&lt;/code&gt; and &lt;code&gt;needs_human&lt;/code&gt;) so your filters have something concrete to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Make.com
&lt;/h2&gt;

&lt;p&gt;Make builds automations as &lt;em&gt;scenarios&lt;/em&gt; made of modules. Make’s AI Agents were rebuilt inside the scenario builder in February 2026, with a reasoning panel that shows the agent’s tool calls. Make also lists a &lt;strong&gt;Human in the Loop&lt;/strong&gt; app for approval steps, but its documentation describes that app as being in closed beta, and a third-party review says it requires an Enterprise plan. The pattern below uses only standard Slack and webhook modules, so it works without that beta.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Decide when to escalate
&lt;/h3&gt;

&lt;p&gt;After your agent or parsing step, add a &lt;strong&gt;Router&lt;/strong&gt; with filter conditions on the structured fields, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;intent&lt;/code&gt; equals a request for custom enterprise pricing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lead_score&lt;/code&gt; above your threshold&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;needs_human&lt;/code&gt; equals true&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Build the context payload
&lt;/h3&gt;

&lt;p&gt;Collect what the AE needs in one place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prospect name, company and email&lt;/li&gt;
&lt;li&gt;Estimated deal size or annual recurring revenue (ARR)&lt;/li&gt;
&lt;li&gt;A two-sentence summary of what the prospect wants and why the agent stopped&lt;/li&gt;
&lt;li&gt;The last few conversation turns or the tool error&lt;/li&gt;
&lt;li&gt;A link to the CRM record&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Set up a Slack app that supports buttons
&lt;/h3&gt;

&lt;p&gt;This is the step most tutorials skip. Buttons only work if Slack knows where to send the click:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a custom Slack app, add the scopes your modules need (at minimum &lt;code&gt;chat:write&lt;/code&gt; to post), and install it. If you change scopes later, Slack will ask you to reinstall.&lt;/li&gt;
&lt;li&gt;Create a Slack connection in Make using that app’s client credentials (under the connection’s advanced settings).&lt;/li&gt;
&lt;li&gt;In a second scenario, add a &lt;strong&gt;Custom Webhook&lt;/strong&gt; trigger and copy its URL. Make’s Slack documentation notes that its Slack watch modules only work with user connections and that an interactive bot needs the Custom Webhook module.&lt;/li&gt;
&lt;li&gt;In the Slack app settings, turn on &lt;strong&gt;Interactivity &amp;amp; Shortcuts&lt;/strong&gt; and paste the webhook URL as the &lt;strong&gt;Request URL&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 4: Post a Block Kit message
&lt;/h3&gt;

&lt;p&gt;In the first scenario, use the Slack &lt;strong&gt;Create a Message&lt;/strong&gt; module (or &lt;strong&gt;Make an API Call&lt;/strong&gt;) with a Block Kit payload. Use a real channel ID, not a channel name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C0123456789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"High-value escalation: Acme Corp needs an AE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"blocks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"section"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mrkdwn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*Urgent sales escalation: enterprise lead stalled*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"section"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mrkdwn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*Company:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Acme Corp (est. $120k ARR)"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mrkdwn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*Reason:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Asked for custom legal terms and a multi-year discount"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"elements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plain_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Claim lead"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deal_12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"action_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claim_lead"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plain_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"View in CRM"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://crm.example.com/deals/12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"action_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open_crm"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The top-level &lt;code&gt;text&lt;/code&gt; is the fallback shown in notifications. Store the message timestamp (&lt;code&gt;ts&lt;/code&gt;) and channel ID that Slack returns, because you need them to update the message later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Handle the click
&lt;/h3&gt;

&lt;p&gt;When someone clicks, Slack sends a &lt;code&gt;block_actions&lt;/code&gt; payload to your Request URL. Keep these points in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledge quickly.&lt;/strong&gt; Slack requires an HTTP 200 response within three seconds, or the user sees an error. Many Make builders add a &lt;strong&gt;Webhook Response&lt;/strong&gt; module early in the scenario to guarantee this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse the payload.&lt;/strong&gt; The interaction arrives as form-encoded data containing a JSON string. Make community guidance suggests running it through a &lt;strong&gt;Parse JSON&lt;/strong&gt; module before filtering on &lt;code&gt;action_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify the claimer.&lt;/strong&gt; The payload includes the Slack user who clicked and the button’s &lt;code&gt;value&lt;/code&gt;, which is where your deal ID goes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update the message.&lt;/strong&gt; Replace the buttons with a status line such as “Claimed by @user” using Make’s Slack &lt;strong&gt;Edit a Message&lt;/strong&gt; module with the saved channel and &lt;code&gt;ts&lt;/code&gt;. Alternatively, use the &lt;code&gt;response_url&lt;/code&gt; in the payload, which Slack allows up to five times within 30 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update the CRM.&lt;/strong&gt; Set the deal owner in HubSpot or Salesforce and send the claimer a direct message with the full context.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Method 2: Zapier
&lt;/h2&gt;

&lt;p&gt;Zapier offers two built-in approval mechanisms, and they behave differently. Choose based on whether you want reps to respond inside Slack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Slack’s Request Approval action
&lt;/h3&gt;

&lt;p&gt;This action belongs to Zapier’s &lt;strong&gt;Slack&lt;/strong&gt; app. It posts a message with an &lt;strong&gt;Approve&lt;/strong&gt; button and, optionally, a &lt;strong&gt;Decline&lt;/strong&gt; button. Configuration includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The message text, which can include fields mapped from earlier steps&lt;/li&gt;
&lt;li&gt;The channel&lt;/li&gt;
&lt;li&gt;Optional &lt;strong&gt;approvers&lt;/strong&gt;. If someone not on the list clicks, Slack shows them a private “not authorized” message and the Zap keeps waiting.&lt;/li&gt;
&lt;li&gt;Custom button labels of up to 75 characters, so “Approve” can read “Take over”&lt;/li&gt;
&lt;li&gt;Optional confirmation dialogs&lt;/li&gt;
&lt;li&gt;Optional button URLs that open for the approver after they decide, which suits a CRM link&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While waiting, the Zap run shows as &lt;em&gt;Waiting / Delay&lt;/em&gt; and the step’s Status field reads “Pending.” It changes to “Approved” or “Declined” after a click. If you omit the Decline button, the only possible outcome is “Approved.” Pending requests expire after 30 days, and Zapier’s documentation describes no shorter configurable timeout for this action. Use &lt;strong&gt;Paths&lt;/strong&gt; or filters after the step to branch on the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: Human in the Loop by Zapier
&lt;/h3&gt;

&lt;p&gt;Zapier’s built-in &lt;strong&gt;Human in the Loop&lt;/strong&gt; tool has a &lt;strong&gt;Request Approval&lt;/strong&gt; action with more control, including a configurable timeout and reminders. It differs from Option A in ways that matter for sales:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is a premium app available on paid plans.&lt;/li&gt;
&lt;li&gt;Reviewers must log in to Zapier to respond. The Slack message links them to the Zap step rather than offering buttons in Slack.&lt;/li&gt;
&lt;li&gt;You must share the Zap with reviewers, and on a Pro plan you can only send requests to yourself.&lt;/li&gt;
&lt;li&gt;The notification can go to email, Slack, or another Zap that you build around the &lt;strong&gt;New Approval Requested&lt;/strong&gt; trigger.&lt;/li&gt;
&lt;li&gt;You set a timeout in minutes, hours, days or weeks, and choose whether the Zap skips the step and continues or ends the run when time runs out. A &lt;strong&gt;Paths&lt;/strong&gt; branch can then reroute a skipped request, for example to a manager channel after 45 minutes.&lt;/li&gt;
&lt;li&gt;On approval, the step’s &lt;code&gt;Decision&lt;/code&gt; field is &lt;code&gt;approved&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The action can’t be placed inside or after a Looping step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For fast, one-click claiming in Slack, Option A is the closer fit. For a minutes-level fallback or a formal review trail, Option B is. If you need Make-style claim tracking by individual user with Zapier, the other route is a custom Slack app whose interactivity URL points at a webhook. That needs the same acknowledgment and parsing care described above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gating the agent itself
&lt;/h3&gt;

&lt;p&gt;In AI by Zapier, you can set &lt;strong&gt;Require approval before running&lt;/strong&gt; per tool. The agent can read data on its own but must wait for a human before an action that can’t be undone, such as emailing a client. Zapier also pauses a run for a human if a single run exceeds 75 tasks, which guards against agents that loop. Place the human gate before the risky action, not after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Be selective.&lt;/strong&gt; If every low-value question pings the sales channel, reps will mute it. Gate escalations on deal size, intent or named accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put the “why” in the message.&lt;/strong&gt; Include a short summary and a CRM link so no one has to dig for context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce single ownership.&lt;/strong&gt; Two reps can click nearly at the same time. Before reassigning, check in your CRM or a data store whether the lead already has an owner, and let only the first claim succeed. Updating the Slack message alone does not prevent a second action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan for silence.&lt;/strong&gt; Decide what happens if no one claims within your target window. Options include re-pinging the channel, paging a manager or routing to another team. Remember that Zapier’s Slack approval step waits up to 30 days by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure claim latency.&lt;/strong&gt; Log the time between the escalation post and the first claim, and review it weekly alongside conversion by response time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the prospect informed.&lt;/strong&gt; While a human is being found, have the workflow send a brief, honest acknowledgment rather than silence, without making commitments the agent can’t keep.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Escalation rules and a well-designed Slack message turn an agent’s dead end into a clean handoff. Make gives you full control over Block Kit and per-user claim tracking, at the cost of setting up a Slack app and webhook. Zapier gives you faster setup through Slack’s Request Approval action, or a more controlled review path through Human in the Loop. Whichever route you choose, give the agent clear stop conditions and give reps one obvious button to take over. Then measure how long escalations wait for an owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zapier.com/blog/zapier-agents-is-now-ai-by-zapier/" rel="noopener noreferrer"&gt;Zapier: Zapier Agents is now AI by Zapier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://help.zapier.com/hc/en-us/articles/35891060646413" rel="noopener noreferrer"&gt;Zapier Help: Set up Slack’s Request Approval action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://help.zapier.com/hc/en-us/articles/38731463206029" rel="noopener noreferrer"&gt;Zapier Help: Request approval with Human in the Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.make.com/en/help/app/slack" rel="noopener noreferrer"&gt;Make Help: Slack app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://make.com/en/help/app/human-in-the-loop" rel="noopener noreferrer"&gt;Make Help: Human in the Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.slack.dev/interactivity/handling-user-interaction" rel="noopener noreferrer"&gt;Slack: Handling user interaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.law360.ca/ca/articles/1804075" rel="noopener noreferrer"&gt;Law360 Canada: Moffatt v. Air Canada&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/mastering-human-in-the-loop-hitl-escalations-routing-make-com-and-zapier-agentic-workflows-to-slack-sales-teams" rel="noopener noreferrer"&gt;https://instachime.com/blog/mastering-human-in-the-loop-hitl-escalations-routing-make-com-and-zapier-agentic-workflows-to-slack-sales-teams&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Technical Guide: Routing Vector Search and Pinecone RAG Intent Spikes to Live Sales War Rooms</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Fri, 23 Oct 2026 01:53:59 +0000</pubDate>
      <link>https://dev.to/instachime/technical-guide-routing-vector-search-and-pinecone-rag-intent-spikes-to-live-sales-war-rooms-5ehn</link>
      <guid>https://dev.to/instachime/technical-guide-routing-vector-search-and-pinecone-rag-intent-spikes-to-live-sales-war-rooms-5ehn</guid>
      <description>&lt;p&gt;&lt;em&gt;Last reviewed October 2026. Vendor products, pricing and compliance programs change often, so check the linked sources before you build.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Traditional intent data, such as third-party ad impressions and basic pageview tracking, often misses the strongest buyer signals. When an enterprise team evaluates a complex SaaS, infrastructure or developer platform, much of the evaluation happens in technical documentation, in self-service search bars, and in AI documentation assistants built on Retrieval-Augmented Generation (RAG).&lt;/p&gt;

&lt;p&gt;Imagine an architect or procurement manager asking a documentation chatbot something like this (an illustrative query):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“What is the latency impact of deploying a single-tenant cluster on AWS FedRAMP High, and how does SOC 2 Type II compliance work for HIPAA workloads?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A question like that discloses deal-relevant parameters: deployment model, compliance regime, and cloud environment. It is also a different kind of signal from a pageview.&lt;/p&gt;

&lt;p&gt;You can capture these signals by instrumenting the RAG query layer in front of your vector database (for example &lt;strong&gt;Pinecone&lt;/strong&gt;). Pinecone executes the similarity search, but the event capture happens in your own application or gateway. You score each query’s similarity to a curated set of buying-intent examples, then push high-scoring events to a live sales “war room” in &lt;strong&gt;Slack&lt;/strong&gt; or &lt;strong&gt;Microsoft Teams&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This guide covers the architecture, the scoring model, a reference implementation, notification design for Slack and Teams, privacy controls, and how to measure results.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Architectural Overview
&lt;/h2&gt;

&lt;p&gt;The pipeline turns unstructured search queries into structured revenue alerts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ User Query ]
       │
       ▼
[ Client Application / RAG Frontend ]
       │
       ├───&amp;gt; 1. Vector Search Pipeline (Pinecone index, "docs" namespace)
       │
       └───&amp;gt; 2. Query Event Emitter (serverless worker / queue / event bus)
                   │
                   ▼
             [ Intent Classifier ]
                   ├── Similarity to intent benchmarks (Pinecone "intent" namespace)
                   ├── Account resolution (SSO / auth token / IP intelligence)
                   └── Optional LLM scoring for borderline cases
                   │
                   ▼
             [ Notification Router ]
                   ├── High intent ──&amp;gt; Slack / Teams war room
                   └── Low intent  ──&amp;gt; Data warehouse / CRM activity history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pipeline Components
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;RAG retrieval layer.&lt;/strong&gt; The client application converts the user’s prompt into a dense embedding and queries a Pinecone index. OpenAI’s &lt;code&gt;text-embedding-3-large&lt;/code&gt; returns 3,072 dimensions by default. Its &lt;code&gt;dimensions&lt;/code&gt; parameter can shorten the vector, and the Pinecone index dimension must match whatever size you choose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event capture layer.&lt;/strong&gt; Asynchronous middleware captures the query text, embedding, session identifier and request metadata. It must not add latency to the user-facing answer, so push the work onto a queue or background worker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intent detection and account resolution.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Account resolution&lt;/em&gt; ties the session to a CRM account. Authenticated SSO domains are the most reliable source. IP-based identification is probabilistic (see Section 5).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Vector similarity scoring&lt;/em&gt; measures how close the query is to a pre-indexed set of “high-value buyer intent” examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notification payload generator.&lt;/strong&gt; It builds Slack Block Kit messages or Microsoft Teams Adaptive Cards containing the query, intent category, confidence, account context and link buttons.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A Pinecone constraint shapes the design: each query targets a single namespace within an index. Keep documentation vectors and intent-benchmark vectors in separate namespaces and run two queries with the same embedding. The Python SDK also offers a &lt;code&gt;query_namespaces&lt;/code&gt; helper that runs queries across namespaces in parallel and merges the results.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Capturing and Classifying Vector Search Intent
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Converting Raw Queries to Intent Scores
&lt;/h3&gt;

&lt;p&gt;Keyword filters miss semantic nuance. A prospect asking &lt;em&gt;“Is there an enterprise SLA for data deletion across multi-region deployments?”&lt;/em&gt; and one asking &lt;em&gt;“How fast do you purge customer records upon contract termination?”&lt;/em&gt; are probing the same procurement concern, even though the two questions share few keywords.&lt;/p&gt;

&lt;p&gt;To measure this, compute the cosine similarity between the query embedding &lt;strong&gt;q&lt;/strong&gt; and each vector &lt;strong&gt;vᵢ&lt;/strong&gt; in a curated set of benchmark intent examples, and take the maximum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;intent_score(q) = max over i of  cos(q, vᵢ)

cos(q, vᵢ) = (q · vᵢ) / (‖q‖ · ‖vᵢ‖)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Pinecone, a &lt;code&gt;top_k=1&lt;/code&gt; query against an index that uses the cosine metric returns this value directly as the match score. How meaningful a score is depends on the index’s distance metric.&lt;/p&gt;

&lt;p&gt;Seed your benchmark set with embedded examples of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pricing, volume discounts and enterprise licensing questions.&lt;/li&gt;
&lt;li&gt;Security and compliance questions (SOC 2, HIPAA, ISO 27001, FedRAMP, data processing agreements).&lt;/li&gt;
&lt;li&gt;Competitor migration guides and benchmark comparisons.&lt;/li&gt;
&lt;li&gt;Enterprise identity and access questions (SSO, SAML, SCIM, RBAC).&lt;/li&gt;
&lt;li&gt;Deployment-model questions (single-tenant, private networking, bring-your-own-cloud).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Write several paraphrases per category and store the category label as metadata so the alert can show it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference Implementation
&lt;/h3&gt;

&lt;p&gt;The example below embeds the query once and runs the normal RAG retrieval. It then scores intent against the benchmark namespace and dispatches a Slack alert in the background, off the user’s critical path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pinecone&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Pinecone&lt;/span&gt;

&lt;span class="c1"&gt;# Clients. Pinecone recommends connecting by index host.
&lt;/span&gt;&lt;span class="n"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pinecone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PINECONE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PINECONE_INDEX_HOST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;openai_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# reads OPENAI_API_KEY
&lt;/span&gt;
&lt;span class="n"&gt;EMBED_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-large&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;EMBED_DIMENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;            &lt;span class="c1"&gt;# must match the Pinecone index dimension
&lt;/span&gt;&lt;span class="n"&gt;INTENT_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.60&lt;/span&gt;            &lt;span class="c1"&gt;# illustrative: calibrate on your own labeled data
&lt;/span&gt;&lt;span class="n"&gt;SLACK_WEBHOOK_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_WAR_ROOM_WEBHOOK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;EMBED_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;EMBED_DIMENSIONS&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;slack_escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Slack requires &amp;amp;, &amp;lt; and &amp;gt; to be escaped in message text
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;amp;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer_and_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;query_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 1) Normal RAG retrieval (one namespace per query)
&lt;/span&gt;    &lt;span class="n"&gt;rag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;include_metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;technical-docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2) Intent scoring against curated benchmark vectors
&lt;/span&gt;    &lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;include_metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sales-intent-benchmarks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# 3) Fire-and-forget so the user's answer is never delayed
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;INTENT_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post_slack_alert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rag&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post_slack_alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;company&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unresolved account&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;top_doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Doc match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n/a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;safe_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;slack_escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;High-intent documentation query: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# notification fallback
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plain_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Intent spike: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;section&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Account:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;slack_escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Intent score:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Category:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;slack_escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Top doc:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;slack_escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;]},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;section&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Search query:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;safe_query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;divider&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;elements&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plain_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Open in Salesforce&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;style&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crm_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plain_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Open war room channel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;war_room_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
            &lt;span class="p"&gt;]},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Slack dispatch failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# replace with real logging/retry
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some notes on this design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Index dimension must match the embedding size.&lt;/strong&gt; The code above requests 1,024 dimensions, so create the index with &lt;code&gt;dimension=1024&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thresholds are not portable.&lt;/strong&gt; The 0.60 above is a placeholder. Cosine score distributions differ by embedding model, so calibrate on a labeled sample of real queries instead of copying someone else’s cutoff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link buttons only open URLs.&lt;/strong&gt; A button that performs an action inside Slack, such as assigning an owner, requires a Slack app with interactivity enabled and an endpoint that verifies Slack’s request signatures. Incoming webhooks alone cannot handle those clicks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack has no deep link that starts a huddle.&lt;/strong&gt; The channel link above opens the war room channel. Reps start the huddle themselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook throughput is limited.&lt;/strong&gt; Slack incoming webhooks are generally limited to about one message per second per channel, which is another reason to debounce (Section 6).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Adding an LLM Scoring Stage
&lt;/h3&gt;

&lt;p&gt;Pure similarity is cheap and fast but blunt. A common refinement is to send only borderline queries (for example, scores within a band below your threshold) to an LLM with a rubric. The rubric asks whether the query signals evaluation, procurement or implementation planning, and requests a structured JSON verdict. Keeping the LLM off the main path controls cost and latency, and logging both scores lets you measure whether the second stage earns its keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. High-Intent vs. Informational Queries
&lt;/h2&gt;

&lt;p&gt;To prevent alert fatigue in Slack or Teams, your classification space must separate &lt;strong&gt;how-do-I-use-this queries&lt;/strong&gt; from &lt;strong&gt;evaluate-and-buy queries&lt;/strong&gt;. The scores below are illustrative only, and actual values depend on your embedding model and benchmark set.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Semantic category&lt;/th&gt;
&lt;th&gt;Illustrative score&lt;/th&gt;
&lt;th&gt;Route action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;“How do I fix a &lt;code&gt;401 Unauthorized&lt;/code&gt; error in the Node SDK?”&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Technical troubleshooting&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Suppress; log to analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;“What is the syntax for creating an index with 1536 dimensions?”&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;General usage&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Suppress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;“How does single-tenant BYOC deployment compare to multi-tenant SaaS compliance?”&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Enterprise architecture / security&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Alert war room&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;“Is there a volume discount schedule above 1 billion query units per month?”&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Pricing / commercials&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;Alert war room&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;“Do you support SOC 2 Type II reports under NDA and a BAA for HIPAA?”&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Compliance / procurement&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;Alert war room and security SE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A useful practice is to review suppressed and alerted queries weekly with sales, then add misclassified examples to the benchmark namespace. The classifier then improves with feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Designing Live Sales War Room Notifications
&lt;/h2&gt;

&lt;p&gt;Cards should give account executives (AEs) and solutions engineers (SEs) enough context to respond without opening five other tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended Card Contents
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Account header:&lt;/strong&gt; company name, domain, tier or segment, and the CRM owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact buyer intent:&lt;/strong&gt; the raw query (truncated and escaped), semantic category, and score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session context:&lt;/strong&gt; recent pages viewed and earlier queries in the same session (for example, the last 30 minutes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieved documentation:&lt;/strong&gt; the top matched doc, which shows what the buyer actually read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action buttons:&lt;/strong&gt; open the CRM record, open the war room channel, and optionally enroll the contact in a sequence in your sales engagement tool.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slack vs. Microsoft Teams
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Slack&lt;/strong&gt; supports Block Kit through incoming webhooks, as shown above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Teams&lt;/strong&gt; requires more care than older tutorials suggest. Microsoft retired Office 365 Connectors, including the classic Incoming Webhook, in Teams. The final switch-off was scheduled for May 18–22, 2026, and Microsoft points developers to webhook-triggered &lt;strong&gt;Workflows&lt;/strong&gt; (Power Automate) as the replacement. Practical implications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send &lt;strong&gt;Adaptive Cards&lt;/strong&gt;, not the legacy MessageCard format. Interactive elements such as buttons are not supported in MessageCard payloads.&lt;/li&gt;
&lt;li&gt;Cards posted through a Workflows webhook display under the default Workflows bot identity, with no custom bot name or icon.&lt;/li&gt;
&lt;li&gt;Workflows are tied to their owner’s account instead of to a team or channel, so plan for ownership changes and departures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A minimal Adaptive Card payload for a Workflows webhook looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attachments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"contentType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/vnd.microsoft.card.adaptive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://adaptivecards.io/schemas/adaptive-card.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AdaptiveCard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TextBlock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Intent spike: Example Corp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bolder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Medium"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FactSet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"facts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Category"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Compliance / Procurement"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Intent score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.91"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TextBlock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Query: Do you support SOC 2 Type II reports under NDA?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"wrap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Action.OpenUrl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open in CRM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/account/123"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Account Resolution: What Still Works in 2026
&lt;/h2&gt;

&lt;p&gt;Alerts are only useful if you know &lt;em&gt;who&lt;/em&gt; is asking. In order of reliability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Authenticated sessions.&lt;/strong&gt; If documentation sits behind login or SSO, the user’s email domain maps cleanly to a CRM account. This is the strongest option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Known-contact cookies and form fills.&lt;/strong&gt; Match prior form submissions or marketing identifiers to existing contacts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP-to-company identification.&lt;/strong&gt; This is probabilistic. IP addresses often belong to ISPs, VPNs, cloud providers or shared offices rather than the buyer’s company, so treat results as a hint and show a confidence level in the card.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The vendor landscape here has shifted. HubSpot acquired Clearbit in late 2023 and folded its capabilities into &lt;strong&gt;Breeze Intelligence&lt;/strong&gt; in 2024. Clearbit’s free tools were retired on April 30, 2025, and the Logo API was sunset in December 2025. Breeze Intelligence is available inside HubSpot rather than as a general-purpose API, and the standalone Clearbit enrichment and Reveal endpoints are being wound down for new customers. If you previously planned around “Clearbit via API,” evaluate current alternatives, or use HubSpot’s built-in visitor identification if your stack is HubSpot-centric. Verify any vendor’s current availability, data coverage and pricing before committing.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Security, Privacy and Rate-Limiting Controls
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Data Minimization and Privacy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sanitize before embedding and before alerting.&lt;/strong&gt; Strip API keys, tokens, passwords and internal IP addresses from queries before generating embeddings or posting to chat. Users sometimes paste secrets into documentation search boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Establish a lawful basis.&lt;/strong&gt; Linking search queries to a named individual is personal data processing under GDPR and similar laws. Work with counsel on whether consent or legitimate interest applies, how you disclose the practice in your privacy notice, and how you honor opt-outs and deletion requests. CCPA/CPRA introduces comparable notice and opt-out obligations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit retention.&lt;/strong&gt; Keep raw queries tied to identifiable users in a short-lived store (for example, Redis with a TTL of 24 hours) unless the account matches an active deal that justifies longer retention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict channel membership.&lt;/strong&gt; War room channels will contain customer questions and account names, so limit access to the revenue team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor posture.&lt;/strong&gt; If you process regulated data, review your vector database vendor’s compliance documentation. Pinecone states it holds SOC 2 Type II certification and supports HIPAA workloads, and it offers a bring-your-own-cloud option that runs in a customer’s own cloud account. Availability by plan, cloud provider and maturity stage changes, so confirm current terms with the vendor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preventing Alert Fatigue
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debounce per account.&lt;/strong&gt; Use rolling windows so a burst of searches produces one summary card instead of a dozen alerts. A simple pattern is an atomic “set if not exists” key with an expiry:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# redis-py: returns True only for the first alert in the window
&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="nf"&gt;send_alert&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;append_to_open_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tier your thresholds.&lt;/strong&gt; Use more sensitive cutoffs for strategic or named accounts and stricter ones for unqualified anonymous traffic. Choose the actual values by testing against labeled history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate-limit outbound posts.&lt;/strong&gt; Queue messages so you stay within webhook throughput limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a feedback control.&lt;/strong&gt; Include a “not relevant” link on each card and feed those labels back into your benchmark set.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Measuring Business Impact
&lt;/h2&gt;

&lt;p&gt;Pairing RAG intent signals with a live war room moves the sales motion from reactive follow-up to near-real-time response. Treat the benefits below as hypotheses to measure, not guarantees.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Faster response.&lt;/strong&gt; The research on speed-to-lead is strong, but it concerns web-form leads. A 2007 Lead Response Management study with InsideSales.com and MIT researcher James Oldroyd found that contacting a web lead within five minutes, versus thirty, made contact about 100 times more likely and qualification about 21 times more likely. A 2011 &lt;em&gt;Harvard Business Review&lt;/em&gt; audit of 2,241 US companies found an average first-response time of about 42 hours, and that firms responding within an hour were nearly seven times more likely to qualify a lead than those that waited an additional hour. Neither study measured documentation-search signals, so don’t assume the same multipliers apply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better-informed first calls.&lt;/strong&gt; Reps can open with the buyer’s actual architecture, security or pricing question instead of generic discovery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measured outcomes, not assumed ones.&lt;/strong&gt; Run a controlled comparison. For example, route a random half of high-intent alerts to the war room and handle the rest through your normal process. Then compare meeting-booked rate, opportunity creation rate, sales-cycle length and rep time spent per alert.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Metrics worth tracking from day one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alert precision (share of alerts reps judge relevant) and alert volume per rep per day.&lt;/li&gt;
&lt;li&gt;Median time from query to first human touch.&lt;/li&gt;
&lt;li&gt;Alert-to-meeting and alert-to-opportunity conversion.&lt;/li&gt;
&lt;li&gt;Suppression-review findings (high-intent queries your classifier missed).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pinecone documentation: &lt;a href="https://docs.pinecone.io/docs/query-data" rel="noopener noreferrer"&gt;Query data&lt;/a&gt; and &lt;a href="https://docs.pinecone.io/guides/operations/bring-your-own-cloud" rel="noopener noreferrer"&gt;Bring your own cloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pinecone: &lt;a href="https://www.pinecone.io/blog/hipaa/" rel="noopener noreferrer"&gt;HIPAA compliance announcement&lt;/a&gt; and &lt;a href="https://pinecone.io/enterprise" rel="noopener noreferrer"&gt;Enterprise overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Microsoft Learn: &lt;a href="https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook" rel="noopener noreferrer"&gt;Create incoming webhooks and Workflows in Teams&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Microsoft 365 Message Center: &lt;a href="https://mc.merill.net/message/MC1181996" rel="noopener noreferrer"&gt;MC1181996, Office 365 connectors retirement in Teams&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hookdeck: &lt;a href="https://hookdeck.com/webhooks/platforms/guide-to-slack-webhooks-features-and-best-practices" rel="noopener noreferrer"&gt;Guide to Slack webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LangChain docs: &lt;a href="https://js.langchain.com/docs/integrations/text_embedding/openai" rel="noopener noreferrer"&gt;OpenAI embeddings and the &lt;code&gt;dimensions&lt;/code&gt; option&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Oldroyd, McElheran and Elkington, “The Short Life of Online Sales Leads,” &lt;em&gt;Harvard Business Review&lt;/em&gt;, March 2011&lt;/li&gt;
&lt;li&gt;ZoomInfo Pipeline: &lt;a href="https://pipeline.zoominfo.com/sales/clearbit-vs-hubspot" rel="noopener noreferrer"&gt;Clearbit vs. HubSpot&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/technical-guide-routing-vector-search-and-pinecone-rag-intent-spikes-to-live-sales-war-rooms" rel="noopener noreferrer"&gt;https://instachime.com/blog/technical-guide-routing-vector-search-and-pinecone-rag-intent-spikes-to-live-sales-war-rooms&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
    <item>
      <title>Routing Snowflake ML Classification &amp; BigQuery ML Churn Scores to Slack: A Near-Real-Time Retention Playbook</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Thu, 22 Oct 2026 01:54:52 +0000</pubDate>
      <link>https://dev.to/instachime/routing-snowflake-ml-classification-bigquery-ml-churn-scores-to-slack-a-near-real-time-retention-563b</link>
      <guid>https://dev.to/instachime/routing-snowflake-ml-classification-bigquery-ml-churn-scores-to-slack-a-near-real-time-retention-563b</guid>
      <description>&lt;h2&gt;
  
  
  Executive Summary: Why the Nightly Batch Score Is Often Too Slow
&lt;/h2&gt;

&lt;p&gt;In SaaS and product-led growth (PLG) businesses, churn rarely arrives without warning. Falling API call volume, repeated authorization errors, spikes in data exports, and seat deallocations all leave footprints in your cloud data warehouse (CDW).&lt;/p&gt;

&lt;p&gt;Many Customer Success (CS) and Revenue Operations teams still run churn models on a nightly schedule. Scores are computed overnight, synced to a CRM such as Salesforce or HubSpot through reverse ETL, and reviewed by Customer Success Managers (CSMs) the next morning. A high-risk signal that appears at 2:00 PM on Monday may not reach a human until Tuesday morning. For a high-value account, that delay can be the difference between a save and a cancellation.&lt;/p&gt;

&lt;p&gt;You can shorten the loop by scoring inside the warehouse as data arrives and pushing only the accounts that cross a risk threshold to Slack. This article shows two patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snowflake:&lt;/strong&gt; a &lt;code&gt;SNOWFLAKE.ML.CLASSIFICATION&lt;/code&gt; model scores new rows from a stream. A triggered task calls Snowflake’s native webhook notifications to post to Slack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BigQuery:&lt;/strong&gt; a BigQuery ML boosted tree model scores accounts. A continuous query exports candidate events to Pub/Sub, and a small Cloud Run function scores them and posts to Slack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two warehouses differ in an important way. Snowflake’s classification model can be called directly from a task. BigQuery continuous queries currently can’t call &lt;code&gt;ML.PREDICT&lt;/code&gt;, so the BigQuery design needs one extra hop. The sections below explain why.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BATCH MODEL (higher latency):
[User event] -&amp;gt; [Raw logs] -&amp;gt; [Nightly ETL] -&amp;gt; [Batch scoring] -&amp;gt; [CRM sync / dashboard] -&amp;gt; [CSM reviews next day]

EVENT-DRIVEN MODEL (latency measured in seconds to minutes):
[User event] -&amp;gt; [Streaming or micro-batch ingestion] -&amp;gt; [In-warehouse scoring] -&amp;gt; [Threshold filter] -&amp;gt; [Slack alert]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A note on naming: Snowflake’s classification feature is now documented under “ML Functions” in its AI &amp;amp; ML section, separate from the LLM-based Cortex AI Functions. It is a gradient boosting model trained with SQL, not a large language model. The Snowflake patterns below apply to that SQL-trained model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Foundations: In-Warehouse Predictive Modeling
&lt;/h2&gt;

&lt;p&gt;Scoring next to the data avoids exporting customer telemetry to separate infrastructure. That reduces data movement and governance overhead, and keeps the model’s access rules aligned with your existing warehouse roles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Snowflake ML Classification
&lt;/h3&gt;

&lt;p&gt;According to Snowflake’s documentation, the classification function is powered by a gradient boosting machine. It supports binary and multi-class problems, and the documentation lists customer churn prediction as a common use case. You can’t choose the algorithm or set model parameters manually.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Prepare the training data
&lt;/h4&gt;

&lt;p&gt;Feature columns must be string, numeric, Boolean, or &lt;code&gt;TIMESTAMP_NTZ&lt;/code&gt;. Strings and Booleans are treated as categorical, and numerics as continuous. Every column that isn’t the target is treated as a feature, so leave identifiers such as &lt;code&gt;account_id&lt;/code&gt; out of the training table or view. Otherwise the model will try to learn from them.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Train the churn model
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLASSIFICATION&lt;/span&gt; &lt;span class="n"&gt;churn_classifier_v1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;INPUT_DATA&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;REFERENCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TABLE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANALYTICS.PROD.CUSTOMER_HEALTH_TRAINING'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;TARGET_COLNAME&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'HAS_CHURNED'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CONFIG_OBJECT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'evaluate'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'on_error'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'SKIP'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Training requires an active warehouse. Snowflake recommends an X-Small standard warehouse for prototyping and a Medium Snowpark-optimized warehouse for larger datasets and production workloads. Models are immutable, so to update one you retrain it, typically with &lt;code&gt;CREATE OR REPLACE&lt;/code&gt;. Classification models can’t be cloned, and cloning or replicating a database that contains one currently skips the model.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Evaluate before you pick a threshold
&lt;/h4&gt;

&lt;p&gt;Don’t guess the alert threshold. Evaluate the model and inspect threshold-level metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;churn_classifier_v1&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;SHOW_EVALUATION_METRICS&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;churn_classifier_v1&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;SHOW_GLOBAL_EVALUATION_METRICS&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;churn_classifier_v1&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;SHOW_THRESHOLD_METRICS&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;churn_classifier_v1&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;SHOW_CONFUSION_MATRIX&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;churn_classifier_v1&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;SHOW_FEATURE_IMPORTANCE&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These methods report precision, recall, F1, AUC, a confusion matrix, and metrics at varying probability thresholds. Use them to choose a cutoff that balances missed churners against alert volume. Predicted probabilities from tree ensembles aren’t guaranteed to be calibrated, so a score of 0.85 isn’t literally an 85% chance of churn. Treat the cutoff as a ranking decision and validate it against historical outcomes.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Run inference
&lt;/h4&gt;

&lt;p&gt;Inference data must use the same feature names and types as training data. Extra columns are ignored, and an unseen categorical value isn’t an error. Snowflake’s documentation shows wildcard expansion in an object literal, &lt;code&gt;{*}&lt;/code&gt;, as the way to pass features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;churn_classifier_v1&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PREDICT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INPUT_DATA&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prediction&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LIVE_ACCOUNT_METRICS&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Enterprise'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prediction is an object containing the predicted class, a &lt;code&gt;logs&lt;/code&gt; field, and a probability for each class. Class labels appear as strings such as &lt;code&gt;"True"&lt;/code&gt; and &lt;code&gt;"False"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"True"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"probability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"False"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1241&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"True"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8759&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To read the churn probability, use &lt;code&gt;prediction['probability']['True']::FLOAT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The model’s &lt;code&gt;!PREDICT&lt;/code&gt; method requires the model’s &lt;code&gt;mlconsumer&lt;/code&gt; instance role, while evaluation methods require &lt;code&gt;mladmin&lt;/code&gt;. Grant the narrower role to whichever role runs your alerting task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: BigQuery ML Boosted Trees
&lt;/h3&gt;

&lt;p&gt;BigQuery ML trains a boosted tree classifier through the XGBoost library with a &lt;code&gt;CREATE MODEL&lt;/code&gt; statement. Boosted tree classifiers are used with &lt;code&gt;ML.PREDICT&lt;/code&gt; for classification.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Train the model
&lt;/h4&gt;

&lt;p&gt;Use a 0/1 label so the probability array is easy to filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="nv"&gt;`project_id.retention_ml.churn_xgb_model`&lt;/span&gt;
&lt;span class="k"&gt;OPTIONS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;MODEL_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'BOOSTED_TREE_CLASSIFIER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;INPUT_LABEL_COLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'has_churned'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="n"&gt;AUTO_CLASS_WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;MAX_ITERATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;logins_last_7d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;seat_utilization_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;open_p1_tickets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;license_exports_30d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;has_churned&lt;/span&gt;  &lt;span class="c1"&gt;-- INT64: 1 = churned, 0 = retained&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project_id.retention_ml.training_dataset`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the model with &lt;code&gt;ML.EVALUATE&lt;/code&gt; before putting it in front of CSMs. Churn is usually an imbalanced problem, which is why &lt;code&gt;AUTO_CLASS_WEIGHTS&lt;/code&gt; is a reasonable starting option.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Score accounts
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prob&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;churn_probability&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PREDICT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="nv"&gt;`project_id.retention_ml.churn_xgb_model`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`project_id.retention_ml.streaming_account_metrics`&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="k"&gt;UNNEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicted_has_churned_probs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  An important limitation: continuous queries can’t call &lt;code&gt;ML.PREDICT&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;It’s tempting to run this scoring query as a BigQuery continuous query. Google’s documentation, last updated September 2026, lists the supported AI and ML functions in continuous queries. That list covers &lt;code&gt;AI.GENERATE&lt;/code&gt;, &lt;code&gt;AI.GENERATE_TEXT&lt;/code&gt;, &lt;code&gt;ML.UNDERSTAND_TEXT&lt;/code&gt;, &lt;code&gt;ML.TRANSLATE&lt;/code&gt;, and &lt;code&gt;ML.NORMALIZER&lt;/code&gt;. It states that BigQuery ML functions outside the list can’t be used. &lt;code&gt;ML.PREDICT&lt;/code&gt; isn’t on it. Check the current documentation and release notes before you build, since this may change.&lt;/p&gt;

&lt;p&gt;What continuous queries do support makes them a good &lt;em&gt;trigger&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They run SQL continuously over data arriving via the Storage Write API, batch loads, DML inserts, and other sources.&lt;/li&gt;
&lt;li&gt;They can write results to a BigQuery table or export them to Pub/Sub, Bigtable, or Spanner.&lt;/li&gt;
&lt;li&gt;Exporting to Pub/Sub requires running the query with a service account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the practical BigQuery design is a two-stage pipeline. A continuous query filters candidate accounts and publishes them to Pub/Sub, and a downstream function scores them with &lt;code&gt;ML.PREDICT&lt;/code&gt; and posts to Slack.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architectural Blueprint
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Snowflake path
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Live metrics table] -&amp;gt; [Append-only stream] -&amp;gt; [Triggered task]
                                                       |
                                  ML.CLASSIFICATION PREDICT + threshold + dedup
                                                       |
                       SYSTEM$SEND_SNOWFLAKE_NOTIFICATION -&amp;gt; Slack webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  BigQuery path
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Storage Write API / streaming inserts] -&amp;gt; [Metrics table]
        -&amp;gt; [Continuous query: filter candidates, EXPORT DATA to Pub/Sub]
        -&amp;gt; [Cloud Run function: ML.PREDICT + threshold + dedup]
        -&amp;gt; [Slack webhook]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whichever path you choose, put the same controls in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prefilter cheaply.&lt;/strong&gt; Only score accounts that matter, such as enterprise tier or ARR above a business-defined line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicate.&lt;/strong&gt; Don’t alert the same account repeatedly within a cooldown window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make delivery idempotent.&lt;/strong&gt; BigQuery’s documentation warns that continuous queries can occasionally reprocess data and produce duplicate output, so downstream systems must tolerate duplicates.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step-by-Step Execution: Snowflake to Slack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Store the Slack webhook as a secret
&lt;/h3&gt;

&lt;p&gt;Snowflake supports webhook notification integrations for Slack, Microsoft Teams, PagerDuty, Jira, and ServiceNow. For Slack, create a generic-string secret holding only the secret portion of the webhook URL, meaning everything after &lt;code&gt;https://hooks.slack.com/services/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;SECRET&lt;/span&gt; &lt;span class="n"&gt;my_secrets_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_secrets_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slack_webhook_secret&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GENERIC_STRING&lt;/span&gt;
  &lt;span class="n"&gt;SECRET_STRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a dedicated role for this setup rather than &lt;code&gt;ACCOUNTADMIN&lt;/code&gt; where your organization’s policies allow it. Creating the integration requires &lt;code&gt;USAGE&lt;/code&gt; on the secret, and sending notifications requires &lt;code&gt;READ&lt;/code&gt; on the secret plus &lt;code&gt;USAGE&lt;/code&gt; on its database and schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create the webhook notification integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;NOTIFICATION&lt;/span&gt; &lt;span class="n"&gt;INTEGRATION&lt;/span&gt; &lt;span class="n"&gt;churn_slack_webhook_int&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WEBHOOK&lt;/span&gt;
  &lt;span class="n"&gt;ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
  &lt;span class="n"&gt;WEBHOOK_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://hooks.slack.com/services/SNOWFLAKE_WEBHOOK_SECRET'&lt;/span&gt;
  &lt;span class="n"&gt;WEBHOOK_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_secrets_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_secrets_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slack_webhook_secret&lt;/span&gt;
  &lt;span class="n"&gt;WEBHOOK_BODY_TEMPLATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{"text": "SNOWFLAKE_WEBHOOK_MESSAGE"}'&lt;/span&gt;
  &lt;span class="n"&gt;WEBHOOK_HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send a test message before wiring in the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;SEND_SNOWFLAKE_NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEXT_PLAIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SANITIZE_WEBHOOK_CONTENT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Churn alert pipeline test'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INTEGRATION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'churn_slack_webhook_int'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SANITIZE_WEBHOOK_CONTENT&lt;/code&gt; removes template placeholders such as &lt;code&gt;SNOWFLAKE_WEBHOOK_SECRET&lt;/code&gt; from the message. Because the template wraps the message in JSON, test with realistic account names, including ones containing quotation marks, so the payload stays valid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create a stream and an alert log
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;STREAM&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_METRICS_STREAM&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LIVE_ACCOUNT_METRICS&lt;/span&gt;
  &lt;span class="n"&gt;APPEND_ONLY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;-- use only if the table receives inserts, not updates&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHURN_ALERT_LOG&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;account_id&lt;/span&gt;        &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;churn_probability&lt;/span&gt; &lt;span class="nb"&gt;FLOAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;alerted_at&lt;/span&gt;        &lt;span class="n"&gt;TIMESTAMP_LTZ&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Score, filter, deduplicate, and notify
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ROUTE_CHURN_ALERTS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="k"&gt;SQL&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
  &lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="c1"&gt;-- Consuming the stream in this statement advances its offset.&lt;/span&gt;
  &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TEMPORARY&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;scored_batch&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;churn_classifier_v1&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PREDICT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INPUT_DATA&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prediction&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACCOUNT_METRICS_STREAM&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Enterprise'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'probability'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'True'&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nb"&gt;FLOAT&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;churn_probability&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="n"&gt;RESULTSET&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;churn_probability&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;scored_batch&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;churn_probability&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;   &lt;span class="c1"&gt;-- choose from SHOW_THRESHOLD_METRICS&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHURN_ALERT_LOG&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alerted_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hour'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt;
    &lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="n"&gt;acct&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="n"&gt;prob&lt;/span&gt; &lt;span class="nb"&gt;FLOAT&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;churn_probability&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Churn risk '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;churn_probability&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'% for '&lt;/span&gt;
                      &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' (ARR '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;')'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;SEND_SNOWFLAKE_NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEXT_PLAIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SANITIZE_WEBHOOK_CONTENT&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;SNOWFLAKE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INTEGRATION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'churn_slack_webhook_int'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHURN_ALERT_LOG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;churn_probability&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;acct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="s1"&gt;'alerts sent: '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adapt object names, privileges, and the dedup window to your environment, and test the procedure with the role that will own the task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Trigger it from the stream
&lt;/h3&gt;

&lt;p&gt;Triggered tasks run when a stream has data, so you don’t need a fixed schedule. Omit the &lt;code&gt;SCHEDULE&lt;/code&gt; parameter and use a &lt;code&gt;WHEN&lt;/code&gt; clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;TASK&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHURN_ALERT_TASK&lt;/span&gt;
  &lt;span class="n"&gt;WAREHOUSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ML_WH&lt;/span&gt;
  &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;STREAM_HAS_DATA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ANALYTICS.PROD.ACCOUNT_METRICS_STREAM'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
  &lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ROUTE_CHURN_ALERTS&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;TASK&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHURN_ALERT_TASK&lt;/span&gt; &lt;span class="n"&gt;RESUME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to Snowflake’s documentation, triggered tasks run at most every 30 seconds by default. You can lower that to 10 seconds with the &lt;code&gt;USER_TASK_MINIMUM_TRIGGER_INTERVAL_IN_SECONDS&lt;/code&gt; parameter. They use no compute until triggered, and Snowflake runs only one instance of a task at a time. Your actual end-to-end latency also depends on how quickly events land in the source table, so measure it rather than assuming a number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional: richer Slack messages with a Python UDF
&lt;/h3&gt;

&lt;p&gt;The webhook notification integration sends a simple template. For a richer Block Kit layout with fields such as primary risk driver and account owner, call Slack from a Python UDF using an external access integration. The secret is exposed to the handler through the &lt;code&gt;_snowflake&lt;/code&gt; module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;SECRET&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slack_webhook_url&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GENERIC_STRING&lt;/span&gt;
  &lt;span class="n"&gt;SECRET_STRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;NETWORK&lt;/span&gt; &lt;span class="k"&gt;RULE&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slack_egress&lt;/span&gt;
  &lt;span class="k"&gt;MODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EGRESS&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HOST_PORT&lt;/span&gt;
  &lt;span class="n"&gt;VALUE_LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hooks.slack.com:443'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;EXTERNAL&lt;/span&gt; &lt;span class="k"&gt;ACCESS&lt;/span&gt; &lt;span class="n"&gt;INTEGRATION&lt;/span&gt; &lt;span class="n"&gt;slack_eai&lt;/span&gt;
  &lt;span class="n"&gt;ALLOWED_NETWORK_RULES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slack_egress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;ALLOWED_AUTHENTICATION_SECRETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slack_webhook_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SEND_SLACK_CHURN_ALERT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;account_name&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="n"&gt;NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;churn_prob&lt;/span&gt; &lt;span class="nb"&gt;FLOAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;risk_driver&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csm_owner&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;PYTHON&lt;/span&gt;
&lt;span class="n"&gt;RUNTIME_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'3.12'&lt;/span&gt;
&lt;span class="n"&gt;PACKAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'requests'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;HANDLER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'send'&lt;/span&gt;
&lt;span class="n"&gt;EXTERNAL_ACCESS_INTEGRATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slack_eai&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SECRETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'webhook'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slack_webhook_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_snowflake&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;churn_prob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;risk_driver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csm_owner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_snowflake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_generic_secret_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'webhook'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="nv"&gt;"Churn risk: {account_name}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;"blocks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="nv"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"plain_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"High churn risk"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"section"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"mrkdwn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="nv"&gt;"*Account:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;{account_name}"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"mrkdwn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="nv"&gt;"*ARR:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;{arr:,.0f}"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"mrkdwn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="nv"&gt;"*Risk score:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;{churn_prob:.2f}"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"mrkdwn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="nv"&gt;"*Top driver:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;{risk_driver}"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"mrkdwn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="nv"&gt;"*Owner:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="nv"&gt;{csm_owner}"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;]},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Snowflake’s GA Python UDF runtimes currently include 3.10 through 3.13. Pick a version your account supports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About interactive buttons:&lt;/strong&gt; an incoming webhook can post Block Kit layouts, but it can’t receive button clicks. A “Claim Account” button needs a Slack app with interactivity enabled and a request URL that receives the click payload. If you want that, use a Slack app with a bot token and handle the callback in a small service. A plain incoming webhook is fine for one-way alerts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Execution: BigQuery to Slack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Stream events into BigQuery
&lt;/h3&gt;

&lt;p&gt;Continuous queries can process data written to standard BigQuery tables via the Storage Write API, batch loads, &lt;code&gt;INSERT&lt;/code&gt; statements, Pub/Sub BigQuery subscriptions, Dataflow, and a few other methods. Land your telemetry or pre-aggregated health metrics in a standard table, for example &lt;code&gt;retention_ml.streaming_account_metrics&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Set up capacity for continuous queries
&lt;/h3&gt;

&lt;p&gt;Continuous queries don’t run on on-demand pricing. Google’s documentation says they require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Enterprise or Enterprise Plus edition reservation.&lt;/li&gt;
&lt;li&gt;A reservation assignment with the &lt;code&gt;CONTINUOUS&lt;/code&gt; job type.&lt;/li&gt;
&lt;li&gt;A service account if you export to Pub/Sub. A job started with a user account stops after two days, while a service-account job can run up to 150 days before it must be restarted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Budget for this capacity as a fixed cost of the design. Slot autoscaling is recommended, and BigQuery requires a 10-slot threshold per continuous query job to admit it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Publish candidate accounts to Pub/Sub
&lt;/h3&gt;

&lt;p&gt;Because scoring can’t happen inside the continuous query, use it to select accounts that deserve scoring and publish them as JSON. The exported column must be named &lt;code&gt;message&lt;/code&gt;, and Pub/Sub topics with schemas aren’t supported as export targets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;EXPORT&lt;/span&gt; &lt;span class="k"&gt;DATA&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CLOUD_PUBSUB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://pubsub.googleapis.com/projects/PROJECT_ID/topics/churn-candidates'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TO_JSON_STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STRUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arr&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project_id.retention_ml.streaming_account_metrics`&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Enterprise'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;seat_utilization_pct&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;   &lt;span class="c1"&gt;-- cheap rule-based prefilter&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this as a continuous query under a service account. Tune the prefilter so you score a manageable stream rather than every event.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Score and notify from a Cloud Run function
&lt;/h3&gt;

&lt;p&gt;A function triggered by the Pub/Sub topic runs &lt;code&gt;ML.PREDICT&lt;/code&gt; for the candidate account and posts to Slack if the score clears your threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;functions_framework&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.cloud&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bigquery&lt;/span&gt;

&lt;span class="n"&gt;bq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bigquery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;SQL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
SELECT account_id, account_name, arr, p.prob AS churn_probability
FROM ML.PREDICT(
  MODEL `project_id.retention_ml.churn_xgb_model`,
  (SELECT *
   FROM `project_id.retention_ml.streaming_account_metrics`
   WHERE account_id = @account_id
   ORDER BY event_ts DESC
   LIMIT 1)
),
UNNEST(predicted_has_churned_probs) AS p
WHERE p.label = 1
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="nd"&gt;@functions_framework.cloud_event&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cloud_event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cloud_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bigquery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;QueryJobConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;query_parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bigquery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ScalarQueryParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRING&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])]))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;churn_probability&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# set from ML.EVALUATE results
&lt;/span&gt;            &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# inject from Secret Manager
&lt;/span&gt;                &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Churn risk &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;churn_probability&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; for &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (ARR &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production hardening checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inject the webhook URL from Secret Manager rather than hard-coding it.&lt;/li&gt;
&lt;li&gt;Record each alert (account, score, timestamp) in a table and skip accounts already alerted within your cooldown window. Pub/Sub delivery and continuous query reprocessing can both create duplicates.&lt;/li&gt;
&lt;li&gt;Handle HTTP 429 responses from Slack. Slack rate-limits incoming webhooks, commonly cited as about one message per second per webhook with short bursts allowed, and returns a &lt;code&gt;Retry-After&lt;/code&gt; header when you exceed limits.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Alternative: simpler micro-batch scoring
&lt;/h3&gt;

&lt;p&gt;If you don’t need event-by-event latency, skip continuous queries and their reservation requirement. Run a frequent scheduled job or Cloud Run job that executes &lt;code&gt;ML.PREDICT&lt;/code&gt; over recently arrived rows and posts the high-scoring accounts. It’s cheaper and simpler to operate, at the cost of latency tied to the schedule. BigQuery ML also supports exporting some models for serving elsewhere. Check which model types your chosen export path supports before committing to that design.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design Considerations Before You Go Live
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose thresholds with data.&lt;/strong&gt; Use the evaluation outputs (&lt;code&gt;SHOW_THRESHOLD_METRICS&lt;/code&gt; in Snowflake, &lt;code&gt;ML.EVALUATE&lt;/code&gt; in BigQuery ML) to pick a cutoff that matches CS capacity. A model that fires 200 alerts a day will be ignored. One that fires a handful of well-targeted alerts will be acted on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guard against alert fatigue.&lt;/strong&gt; Add cooldown windows, route by account owner or segment, and consider a daily digest for medium-risk accounts alongside immediate alerts for the highest-value ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch for data leakage and drift.&lt;/strong&gt; Make sure training features only use information available at prediction time. Retrain regularly, since Snowflake models are immutable and must be replaced, and BigQuery models should be re-evaluated on recent data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep sensitive data out of Slack.&lt;/strong&gt; Alerts should carry an account name, a score, and a link to your CRM or dashboard, not raw usage logs or personal data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protect your secrets.&lt;/strong&gt; Webhook URLs are bearer secrets. Anyone with the URL can post to the channel. Store them in Snowflake secrets or Google Secret Manager, and rotate them if exposed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure latency yourself.&lt;/strong&gt; The speed of the whole chain depends on ingestion method, warehouse or slot capacity, task trigger intervals, and Slack delivery. Instrument timestamps at each hop and report the real number.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Moving churn scoring from a nightly batch to an event-driven pipeline changes how quickly your team can respond to at-risk accounts. Snowflake’s stream, triggered task, and webhook notification features let you score and alert entirely from SQL. On BigQuery, the same outcome needs a continuous query that exports candidates to Pub/Sub plus a small function that runs &lt;code&gt;ML.PREDICT&lt;/code&gt;, because &lt;code&gt;ML.PREDICT&lt;/code&gt; isn’t currently supported inside continuous queries. In both cases, the model’s value depends on a carefully chosen threshold, deduplicated alerts, and a Slack message that tells the CSM what to do next.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Snowflake: Classification (Snowflake ML Functions) — &lt;a href="https://docs.snowflake.com/en/user-guide/ml-functions/classification" rel="noopener noreferrer"&gt;https://docs.snowflake.com/en/user-guide/ml-functions/classification&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Snowflake: CREATE SNOWFLAKE.ML.CLASSIFICATION — &lt;a href="https://docs.snowflake.com/en/sql-reference/classes/classification/commands/create-classification" rel="noopener noreferrer"&gt;https://docs.snowflake.com/en/sql-reference/classes/classification/commands/create-classification&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Snowflake: Triggered tasks — &lt;a href="https://docs.snowflake.com/en/user-guide/tasks-triggered" rel="noopener noreferrer"&gt;https://docs.snowflake.com/en/user-guide/tasks-triggered&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Snowflake: Sending webhook notifications — &lt;a href="https://docs.snowflake.com/en/en/user-guide/notifications/webhook-notifications" rel="noopener noreferrer"&gt;https://docs.snowflake.com/en/en/user-guide/notifications/webhook-notifications&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Snowflake: API reference for access to secrets — &lt;a href="https://docs.snowflake.com/developer-guide/external-network-access/secret-api-reference" rel="noopener noreferrer"&gt;https://docs.snowflake.com/developer-guide/external-network-access/secret-api-reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Snowflake: Creating Python UDFs — &lt;a href="https://docs.snowflake.com/developer-guide/udf/python/udf-python-creating" rel="noopener noreferrer"&gt;https://docs.snowflake.com/developer-guide/udf/python/udf-python-creating&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Cloud: Introduction to BigQuery continuous queries — &lt;a href="https://docs.cloud.google.com/bigquery/docs/continuous-queries-introduction" rel="noopener noreferrer"&gt;https://docs.cloud.google.com/bigquery/docs/continuous-queries-introduction&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Cloud: Export data to Pub/Sub (reverse ETL) — &lt;a href="https://docs.cloud.google.com/bigquery/docs/export-to-pubsub" rel="noopener noreferrer"&gt;https://docs.cloud.google.com/bigquery/docs/export-to-pubsub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Cloud: CREATE MODEL for boosted trees using XGBoost — &lt;a href="https://cloud.google.com/bigquery-ml/docs/reference/standard-sql/bigqueryml-syntax-create-boosted-tree" rel="noopener noreferrer"&gt;https://cloud.google.com/bigquery-ml/docs/reference/standard-sql/bigqueryml-syntax-create-boosted-tree&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Slack: Sending messages using incoming webhooks — &lt;a href="https://api.slack.com/messaging/webhooks" rel="noopener noreferrer"&gt;https://api.slack.com/messaging/webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/routing-snowflake-ml-classification-bigquery-ml-churn-scores-to-slack-a-near-real-time-retention-playbook" rel="noopener noreferrer"&gt;https://instachime.com/blog/routing-snowflake-ml-classification-bigquery-ml-churn-scores-to-slack-a-near-real-time-retention-playbook&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
    </item>
    <item>
      <title>Routing OpenAI Operator and Browser-Use Workflows to Human AEs in Slack: A Complete Playbook</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Wed, 21 Oct 2026 04:56:25 +0000</pubDate>
      <link>https://dev.to/instachime/routing-openai-operator-and-browser-use-workflows-to-human-aes-in-slack-a-complete-playbook-d0i</link>
      <guid>https://dev.to/instachime/routing-openai-operator-and-browser-use-workflows-to-human-aes-in-slack-a-complete-playbook-d0i</guid>
      <description>&lt;p&gt;As autonomous web-navigation tools and agent frameworks—such as OpenAI operator workflows, browser-use Python libraries, and multi-agent systems—transform modern sales operations, organizations face a critical boundary: &lt;strong&gt;the limit of autonomous decision-making.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Autonomous agents excel at multi-turn research, form-filling, navigating complex enterprise dashboards, populating CRMs, and aggregating web data. However, high-stakes commercial transactions—such as final pricing negotiations, custom enterprise contract exceptions, security compliance approvals, and relationship-driven closing calls—still require human empathy, nuance, and strategic judgment.&lt;/p&gt;

&lt;p&gt;Successfully bridging autonomous web-navigation agents with human Account Executives (AEs) requires a resilient, low-latency orchestration layer. This complete playbook outlines how to architect an end-to-end pipeline that routes OpenAI operator and browser-use workflows directly to human AEs in Slack using human-in-the-loop (HITL) patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.google.com/search?q=%231-the-architecture-from-browser-automation-to-human-action" rel="noopener noreferrer"&gt;The Architecture: From Browser Automation to Human Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/search?q=%232-defining-the-human-in-the-loop-hitl-trigger-matrix" rel="noopener noreferrer"&gt;Defining the Human-in-the-Loop (HITL) Trigger Matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/search?q=%233-step-by-step-technical-implementation" rel="noopener noreferrer"&gt;Step-by-Step Technical Implementation&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Configuring OpenAI Agent Interruption Hooks&lt;/li&gt;
&lt;li&gt;Step 2: Building the Middleware Orchestrator&lt;/li&gt;
&lt;li&gt;Step 3: Dispatching Rich Interactive Slack Blocks

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.google.com/search?q=%234-handling-ae-responses-and-resuming-agent-workflows" rel="noopener noreferrer"&gt;Handling AE Responses and Resuming Agent Workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/search?q=%235-production-best-practices-security-and-edge-cases" rel="noopener noreferrer"&gt;Production Best Practices, Security, and Edge Cases&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. The Architecture: From Browser Automation to Human Action
&lt;/h2&gt;

&lt;p&gt;To understand how browser agents and human reps collaborate, consider a typical enterprise sales motion: an inbound prospect requests a custom sandbox setup, specialized volume discounting, or compliance verification through a web portal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Prospect Web Portal / CRM]
           │
           ▼  (Triggers Autonomous Run)
┌──────────────────────────────────────┐
│       OpenAI Operator / Agent        │
│    (Executes low-risk browser steps) │
└──────────────────┬───────────────────┘
           │
           ├─► [Low Risk: Auto-Continue]  ──► [Complete Task]
           │
           ▼  (High Risk / Threshold Hit)
┌──────────────────────────────────────┐
│        HITL Interruption Hook        │
└──────────────────┬───────────────────┘
           │
           ▼  (Payload + Context Extraction)
┌──────────────────────────────────────┐
│     Orchestrator Middleware Server   │
└──────────────────┬───────────────────┘
           │
           ▼  (Socket Mode / Interactive Blocks)
┌──────────────────────────────────────┐
│       Slack Channel / AE DM          │
│    ([Approve] / [Reject] / [Escal])  │
└──────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system operates across three core layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Execution Tier:&lt;/strong&gt; Autonomous browser-use frameworks or OpenAI operator scripts execute repetitive tasks inside a secure cloud sandbox (e.g., pulling data from LinkedIn Sales Navigator, logging into internal billing tools, or reviewing custom terms).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Interruption Tier:&lt;/strong&gt; Utilizing native agent interruption hooks (such as the OpenAI Agents SDK's &lt;code&gt;human_in_the_loop&lt;/code&gt; configurations), the agent pauses execution state whenever it hits an ambiguous rule, financial threshold, or critical form submission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Communication Tier:&lt;/strong&gt; Middleware catches the paused run state, serializes the visual and text context, and sends an interactive message card to the relevant Account Executive via Slack.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. Defining the Human-in-the-Loop (HITL) Trigger Matrix
&lt;/h2&gt;

&lt;p&gt;Sending every minor agent action to Slack results in notification fatigue and defeats the purpose of automation. Your playbook must strictly define &lt;strong&gt;when&lt;/strong&gt; an agent should hand off control to a human AE.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Trigger Category&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Example Scenario&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Action Required in Slack&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Financial / Commercial&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Request exceeds standard automated boundaries.&lt;/td&gt;
&lt;td&gt;Prospect requests a 25% discount on an annual contract tier.&lt;/td&gt;
&lt;td&gt;AE reviews margin impact and approves/denies custom quote.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Security &amp;amp; Compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent encounters non-standard legal/security clauses.&lt;/td&gt;
&lt;td&gt;Vendor risk assessment form requires human sign-off on data retention.&lt;/td&gt;
&lt;td&gt;AE assigns compliance officer or approves legal exception.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ambiguous Intent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The UI yields conflicting data or unexpected states.&lt;/td&gt;
&lt;td&gt;CRM account matching returns two identical corporate entities.&lt;/td&gt;
&lt;td&gt;AE selects correct Salesforce/HubSpot account ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Final Execution Gate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High-consequence actions finalizable by humans only.&lt;/td&gt;
&lt;td&gt;Submitting final binding contract via procurement portal.&lt;/td&gt;
&lt;td&gt;AE performs final verification before execution.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  3. Step-by-Step Technical Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Configuring OpenAI Agent Interruption Hooks
&lt;/h3&gt;

&lt;p&gt;When configuring your agent loop, designate sensitive actions or custom tools as requiring explicit approval. Using Python and the OpenAI Agents SDK, you can set up functions with &lt;code&gt;needs_approval=True&lt;/code&gt; or implement custom interruption states.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function_tool&lt;/span&gt;

&lt;span class="nd"&gt;@function_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;needs_approval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit_enterprise_contract_portal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contract_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# This tool execution will automatically trigger an interruption 
&lt;/span&gt;    &lt;span class="c1"&gt;# requiring human validation before the browser proceeds.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Contract &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;contract_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; submitted for $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;sales_operator_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SalesOps Operator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You navigate enterprise portals to prep and queue contracts.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;submit_enterprise_contract_portal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Building the Middleware Orchestrator
&lt;/h3&gt;

&lt;p&gt;When the agent run encounters an interruption, it pauses and yields a state object containing pending tool calls. Your backend server must catch this state, extract the metadata, and prepare it for transmission.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_sales_workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_operator_agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Check if the run has paused for human-in-the-loop approval
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_paused&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;interruptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_interruptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;interruption&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;interruptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Dispatch details to Slack notification pipeline
&lt;/span&gt;            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;dispatch_approval_to_slack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;run_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;interruption_data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;interruption&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Dispatching Rich Interactive Slack Blocks
&lt;/h3&gt;

&lt;p&gt;To make the handoff actionable for the AE, use Slack's Block Kit framework. This allows the AE to see context (e.g., company name, requested discount, screenshot summary) and take immediate action via buttons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;slack_sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebClient&lt;/span&gt;

&lt;span class="n"&gt;slack_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WebClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dispatch_approval_to_slack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interruption_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ae_slack_channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#ae-deal-approvals&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Extract details from the interrupted tool call
&lt;/span&gt;    &lt;span class="n"&gt;tool_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interruption_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="n"&gt;tool_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interruption_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;

    &lt;span class="n"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;section&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🚨 *AE Action Required: Browser Agent Paused*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;• *Tool:* `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;• *Parameters:* ```
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
```&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;elements&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plain_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Approve &amp;amp; Resume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;style&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approve_agent_action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;interruption_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plain_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ Reject &amp;amp; Pivot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;style&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;danger&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reject_agent_action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;interruption_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;slack_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat_postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ae_slack_channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;An OpenAI operator workflow requires your review.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;blocks&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Handling AE Responses and Resuming Agent Workflows
&lt;/h2&gt;

&lt;p&gt;When the AE clicks either &lt;strong&gt;Approve&lt;/strong&gt; or &lt;strong&gt;Reject&lt;/strong&gt; in Slack, Slack sends an interactive payload back to your server via Socket Mode or an HTTP Request URL. Your application must securely ingest this payload, authenticate the user, and feed the decision back into the paused agent runner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;slack_bolt&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;signing_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_SIGNING_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approve_agent_action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_approval_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Parse session and interruption IDs from the button value
&lt;/span&gt;    &lt;span class="n"&gt;action_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interruption_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Retrieve stored server-side RunState (using your DB/cache layer)
&lt;/span&gt;    &lt;span class="n"&gt;run_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_run_state_from_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Apply approval decision
&lt;/span&gt;    &lt;span class="n"&gt;run_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interruption_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Resume execution asynchronously
&lt;/span&gt;    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resume_and_complete_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nd"&gt;@app.action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reject_agent_action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_rejection_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;action_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interruption_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;run_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_run_state_from_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;run_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interruption_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rejection_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AE rejected transaction parameters.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resume_and_complete_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;run_state.approve()&lt;/code&gt; or &lt;code&gt;run_state.reject()&lt;/code&gt; is executed, the agent resumes its multi-turn loop—either completing the web transaction or adapting its strategy based on the human feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Production Best Practices, Security, and Edge Cases
&lt;/h2&gt;

&lt;p&gt;When deploying autonomous browser workflows tied to human revenue teams, scale and security must be engineered from day one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Strict User Authentication &amp;amp; RBAC:&lt;/strong&gt; Never trust a Slack payload blindly. Map the Slack User ID (&lt;code&gt;U12345&lt;/code&gt;) to your CRM user database to ensure only assigned AEs with proper deal authorization can approve high-value overrides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preventing Approval Fatigue:&lt;/strong&gt; Set rigid financial brackets. If an agent requests approval for every $50 line-item adjustment, AEs will rapidly ignore notifications. Reserve Slack interruptions strictly for tier-breaking decisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual Snapshots in Slack:&lt;/strong&gt; Attach visual browser states (such as compressed screenshots captured by browser automation runtimes right before the pause) directly into the Slack thread so the AE can visually verify what the agent was looking at (e.g., the exact pricing table or error message on the target portal).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency &amp;amp; State Locking:&lt;/strong&gt; Use distributed locks (e.g., Redis) around &lt;code&gt;run_state&lt;/code&gt; modifications. If two AEs accidentally click review buttons on a shared channel, your backend must prevent race conditions and duplicate approvals.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/routing-openai-operator-and-browser-use-workflows-to-human-aes-in-slack-a-complete-playbook" rel="noopener noreferrer"&gt;https://instachime.com/blog/routing-openai-operator-and-browser-use-workflows-to-human-aes-in-slack-a-complete-playbook&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Routing Product-Qualified Accounts (PQAs) vs. Product-Qualified Leads (PQLs): Why Different SLA Tiers Matter</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Tue, 20 Oct 2026 01:48:11 +0000</pubDate>
      <link>https://dev.to/instachime/routing-product-qualified-accounts-pqas-vs-product-qualified-leads-pqls-why-different-sla-33am</link>
      <guid>https://dev.to/instachime/routing-product-qualified-accounts-pqas-vs-product-qualified-leads-pqls-why-different-sla-33am</guid>
      <description>&lt;p&gt;In Product-Led Growth (PLG) and Product-Led Sales (PLS) motions, product usage is one of the strongest signals of buyer intent. Many Revenue Operations (RevOps) teams still hit a bottleneck because they treat every sign-up or activation event the same way.&lt;/p&gt;

&lt;p&gt;Routing a single highly engaged user to sales might produce a small self-serve upgrade or a one-seat expansion. Missing a quiet group of 20 users inside a large enterprise who are hitting usage limits, connecting integrations and visiting pricing pages can mean a much larger deal never gets worked.&lt;/p&gt;

&lt;p&gt;To close this gap, RevOps leaders need to separate &lt;strong&gt;Product-Qualified Leads (PQLs)&lt;/strong&gt; from &lt;strong&gt;Product-Qualified Accounts (PQAs)&lt;/strong&gt;. Each signal needs its own Service Level Agreement (SLA) and routing path. High-value PQAs go quickly to senior Account Executives (AEs), and individual PQLs stay in automated or lower-touch paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the Distinction: PQLs vs. PQAs
&lt;/h2&gt;

&lt;p&gt;Both metrics come from product telemetry and event data, but they differ in scope, buying authority and likely Annual Contract Value (ACV).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              ┌────────────────────────────┐
              │     PRODUCT EVENT DATA     │
              └─────────────┬──────────────┘
                            │
          ┌─────────────────┴─────────────────┐
          ▼                                   ▼
┌───────────────────┐               ┌───────────────────┐
│   PQL ARCHETYPE   │               │   PQA ARCHETYPE   │
│  Individual user  │               │  Multi-user team  │
├───────────────────┤               ├───────────────────┤
│ • One active user │               │ • Several active  │
│ • Localized use   │               │   domain users    │
│ • Personal value  │               │ • Admin settings  │
│   realized        │               │ • SSO / security  │
│                   │               │ • ICP fit         │
└─────────┬─────────┘               └─────────┬─────────┘
          │                                   │
          ▼                                   ▼
┌───────────────────┐               ┌───────────────────┐
│ Lower-touch path  │               │ Priority path     │
│ Example SLA:      │               │ Example SLA:      │
│ hours to 2 days   │               │ minutes           │
│ (nurture / SDR)   │               │ (senior AE)       │
└───────────────────┘               └───────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The SLA values above are illustrative starting points, not industry standards. Tune them to your own rep capacity and conversion data.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a Product-Qualified Lead (PQL)?
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;PQL&lt;/strong&gt; is an individual user who has crossed a defined threshold of engagement or value in your product. The signal comes from in-product behavior, not from a form fill or content download.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; Individual behavior and single-user activation events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typical indicators:&lt;/strong&gt; Completing onboarding, hitting a usage-frequency milestone, or reaching the “aha moment” specific to your product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; Adoption, habit formation, and self-serve or low-touch conversion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is a Product-Qualified Account (PQA)?
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;PQA&lt;/strong&gt; is an organization whose aggregated user behavior, combined with firmographic fit, suggests readiness for a commercial conversation. Definitions vary by company. Some use a minimum number of active users, some use the share of users who are individually PQL-qualified, and some use volume, velocity and feature breadth. The common idea is that account-level behavior, not one person’s behavior, triggers the play.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; Collective usage, cross-team collaboration, enterprise features and organizational fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typical indicators:&lt;/strong&gt; Multiple active users on the same company domain, enterprise integrations (for example Okta, Salesforce or Workday), admin and security settings, and usage approaching plan limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; Multi-seat or company-wide deployment, security and compliance review, and a negotiated contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Product-led sales practitioners point to a further reason the two objects differ. In an SMB account, the buyer is often one of the active users. In a mid-market or enterprise account, the person who signs the contract frequently isn’t using the product at all, so a PQL-only view misses the real buyer (&lt;a href="https://priyabhatia.substack.com/p/pqls-vs-pqas" rel="noopener noreferrer"&gt;Priya Bhatia, PQLs vs. PQAs&lt;/a&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  The Operational Risk of Collapsing PQLs and PQAs
&lt;/h2&gt;

&lt;p&gt;When PQLs and PQAs share one queue and one SLA, two failure modes appear.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rep fatigue and distrust of the data.&lt;/strong&gt; If senior AEs receive a steady stream of single-seat users with no budget or buying authority, they learn to ignore product-signal alerts. Real enterprise signals then get ignored along with the noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delayed enterprise outreach.&lt;/strong&gt; Enterprise purchases are group decisions. Gartner reports that complex B2B purchases typically involve &lt;a href="https://www.leandata.com/blog/buying-groups-todays-b2b-go-to-market-motions" rel="noopener noreferrer"&gt;six to ten decision-makers&lt;/a&gt;, and the evaluation often starts with scattered team sign-ups rather than a demo request. If that account is treated as one more free-tier sign-up, outreach can lag by days or never happen, which gives competitors time to get in first.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Separating User Activity from Organizational Intent
&lt;/h2&gt;

&lt;p&gt;To route enterprise opportunities well, RevOps needs a data layer that turns individual event streams into account-level intelligence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     USER-LEVEL EVENTS                    ACCOUNT-LEVEL INTENT
┌───────────────────────────┐          ┌───────────────────────────┐
│ • Individual sign-ups     │          │ • Growing count of        │
│ • Personal workspaces     │          │   domain-matched users    │
│ • Basic feature usage     │ ───────&amp;gt; │ • SSO / SAML configured   │
│ • Exporting files         │ AGGREGATE│ • Security / pricing page │
└───────────────────────────┘ INTO PQA │   visits                  │
                                       │ • Plan limits approached  │
                                       │ • ICP / firmographic fit  │
                                       └───────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Account Roll-Up and Identity Resolution
&lt;/h3&gt;

&lt;p&gt;A PQA engine depends on reliable user-to-account mapping.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain matching:&lt;/strong&gt; Group users by company email domain (for example &lt;code&gt;user@company.com&lt;/code&gt;) and exclude public webmail domains such as &lt;code&gt;gmail.com&lt;/code&gt;, &lt;code&gt;yahoo.com&lt;/code&gt; and &lt;code&gt;outlook.com&lt;/code&gt;. Add allow and block lists for edge cases such as consultants, agencies and universities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corporate parent enrichment:&lt;/strong&gt; Use a firmographic data provider to roll subsidiaries and regional domains up to a parent account, and to add employee count, industry and revenue. Check the current state of your vendor before you build on it. For example, HubSpot acquired Clearbit in late 2023 and relaunched its capabilities as Breeze Intelligence. Clearbit’s free tools (the free platform, Clearbit Connect, the Weekly Visitor Report and the TAM Calculator) were &lt;a href="https://clearbit.com/changelog/2025-02-13" rel="noopener noreferrer"&gt;discontinued on April 30, 2025&lt;/a&gt;, and the free Logo API was scheduled to sunset on December 1, 2025. Breeze Intelligence now runs inside HubSpot rather than as a standalone product. Teams not on HubSpot generally need other providers such as ZoomInfo, Apollo or Cognism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lead-to-account (L2A) matching:&lt;/strong&gt; Domain-only matching misses subsidiaries, personal-email sign-ups and renamed companies. Dedicated L2A tools, such as &lt;a href="https://usagepricing.com/blueprint/stack/leandata" rel="noopener noreferrer"&gt;LeanData&lt;/a&gt;, use fuzzy matching to tie product users to the right CRM account and owner. Check that product-side users and CRM-side contacts resolve to the same account record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer status check:&lt;/strong&gt; Before anything is scored, test whether the account is already a customer, an open opportunity, or an excluded segment such as a competitor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Combine Fit, Usage and Intent
&lt;/h3&gt;

&lt;p&gt;A common framework scores accounts on three dimensions: fit (how closely the account matches your ICP), product usage (user-level and account-level events that correlate with retention and conversion), and intent (such as pricing-page visits, “talk to sales” clicks or seat additions). The best candidates combine strong fit with high usage, and accounts with great fit but low usage are better nurtured than ignored (&lt;a href="https://pocus.notion.site/How-to-analyze-usage-data-to-create-Product-Qualified-Lead-PQL-definitions-3e22c597420b4c99b8bbb39b3d8d9a4f" rel="noopener noreferrer"&gt;Pocus guide to defining PQLs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Derive thresholds from your own history. Look at which behaviors preceded paid conversion and expansion among your best customers, rather than copying another company’s definition. Review thresholds again after any pricing or packaging change.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Weight Enterprise-Intent Signals
&lt;/h3&gt;

&lt;p&gt;Typical high-intent account signals include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple active users on the same domain, across more than one team&lt;/li&gt;
&lt;li&gt;SSO or SAML setup, or visits to security and compliance pages&lt;/li&gt;
&lt;li&gt;Enterprise integrations connected&lt;/li&gt;
&lt;li&gt;Admin settings and workspace-level configuration&lt;/li&gt;
&lt;li&gt;Usage approaching or exceeding plan limits&lt;/li&gt;
&lt;li&gt;Pricing or “contact sales” page visits from known account users&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Track Recency and Momentum
&lt;/h3&gt;

&lt;p&gt;Accounts move in and out of PQA status. Use time windows (for example, signals in the last 14 or 30 days) and look at acceleration as well as totals. Retire accounts from the active queue if they stop engaging, so sales effort stays focused.&lt;/p&gt;




&lt;h2&gt;
  
  
  Designing Tiered SLAs
&lt;/h2&gt;

&lt;p&gt;The case for fast response comes mainly from inbound-lead research:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A widely cited Lead Response Management study (research led by James Oldroyd with InsideSales.com, 2007) found that contacting a web lead within 5 minutes rather than 30 made the lead about 21 times more likely to qualify and about 100 times more likely to be contacted at all.&lt;/li&gt;
&lt;li&gt;A 2011 Harvard Business Review study found that firms attempting contact within an hour were nearly seven times as likely to qualify a lead as those that waited even one more hour.&lt;/li&gt;
&lt;li&gt;One caution: the “five-minute” figure is often attributed to HBR, but it traces back to the 2007 study (&lt;a href="https://www.usecarly.com/blog/speed-to-lead-statistics/" rel="noopener noreferrer"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These studies covered web-form leads, not product-usage signals, so treat them as directional. They support the principle that response time matters most where intent and deal value are highest. They don’t prove that a specific product-signal SLA will produce a specific lift.&lt;/p&gt;

&lt;p&gt;Here is a suggested tier structure. All times are measured in business hours and are starting points to tune against your capacity.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;Suggested first-touch SLA&lt;/th&gt;
&lt;th&gt;Escalation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1. Strategic PQA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Target-ICP account with multiple domain users plus enterprise signals (SSO, security review, limits)&lt;/td&gt;
&lt;td&gt;Named owner or senior AE&lt;/td&gt;
&lt;td&gt;~15 minutes&lt;/td&gt;
&lt;td&gt;Manager alert, then reassign if untouched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2. Mid-market PQA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fit account with growing multi-user usage, fewer enterprise signals&lt;/td&gt;
&lt;td&gt;AE pod, round-robin&lt;/td&gt;
&lt;td&gt;~1 hour&lt;/td&gt;
&lt;td&gt;Reassign within the pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3. High-intent PQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Individual user with strong intent (pricing visit, limit hit) in a low-fit or small account&lt;/td&gt;
&lt;td&gt;SDR or inside sales&lt;/td&gt;
&lt;td&gt;Same business day&lt;/td&gt;
&lt;td&gt;Fall back to nurture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. Standard PQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Activation milestone reached, no account-level signal&lt;/td&gt;
&lt;td&gt;Automated lifecycle messaging&lt;/td&gt;
&lt;td&gt;No human SLA&lt;/td&gt;
&lt;td&gt;Re-score if signals change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5. Existing customer expansion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PQA signals in an account that already pays&lt;/td&gt;
&lt;td&gt;Account manager or CSM&lt;/td&gt;
&lt;td&gt;Per account-management SLA&lt;/td&gt;
&lt;td&gt;Notify the owner AE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Routing Architecture
&lt;/h2&gt;

&lt;p&gt;A routing flow that supports these tiers is account-first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product event → Identity resolution → Customer / opportunity check
        │
        ├── Existing customer?     → Route to AM / CSM
        ├── Open opportunity?      → Notify opportunity owner
        └── Net-new account?
               │
               ├── Aggregate account score
               │      ├── Tier 1 → Named / senior AE (alert + task)
               │      ├── Tier 2 → AE pod
               │      ├── Tier 3 → SDR / inside sales
               │      └── Tier 4 → Automated nurture
               │
               └── SLA timer → Escalate → Reassign if no first touch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key design principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Route to the account owner first.&lt;/strong&gt; If an active account already has an owner, send the signal there. Round-robin alone fails once account ownership, territories or product specialization matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collapse PQLs into their PQA.&lt;/strong&gt; When a user belongs to a qualifying account, suppress the individual PQL alert and send one account-level alert listing the users and signals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include context in the alert.&lt;/strong&gt; Reps respond better to “engineering and marketing are both running weekly dashboards and SSO was configured yesterday” than to a bare score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce SLAs with timers and reassignment.&lt;/strong&gt; Use SLA timers, manager escalations and automatic reassignment. Orchestration tools such as LeanData and Chili Piper cover &lt;a href="https://usagepricing.com/blueprint/stack/leandata" rel="noopener noreferrer"&gt;routing, SLA timers and instant scheduling&lt;/a&gt;, and many stacks run both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep a fallback queue.&lt;/strong&gt; Records that fail matching or routing should land in a monitored queue rather than disappear.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tooling Notes
&lt;/h2&gt;

&lt;p&gt;The product-signal tooling market has shifted recently. Pocus, a prominent PLS platform used by companies including Asana, Canva and Monday.com, was &lt;a href="https://thenextweb.com/news/apollo-acquires-pocus-revenue-intelligence-gtm" rel="noopener noreferrer"&gt;acquired by Apollo.io&lt;/a&gt; in March 2026, and its capabilities are being folded into Apollo’s platform. If you’re evaluating or already using standalone product-signal tools, confirm current packaging and roadmap with the vendor. Whatever you choose, the underlying requirements stay the same: reliable identity resolution, a scoring model derived from your own data, and routing with enforceable SLAs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measuring Whether It Works
&lt;/h2&gt;

&lt;p&gt;Benchmarks vary widely by source and company stage. Published figures for PQL conversion range from low double digits to the 30s, against single digits for MQLs, and the numbers come from differing definitions. Rely on your own baselines, and track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SLA attainment:&lt;/strong&gt; the share of PQAs and PQLs touched within their tier’s window&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed to first touch&lt;/strong&gt; by tier&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PQA-to-opportunity and PQL-to-paid conversion&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Win rate, ACV and sales-cycle length&lt;/strong&gt; by tier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False-positive rate:&lt;/strong&gt; alerts that reps reject as unqualified&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reassignment rate:&lt;/strong&gt; a high rate usually points to matching or ownership problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If PQA volume exceeds sales capacity, tighten thresholds. If PQL-to-sales-qualified conversion is low, look at packaging, pricing friction and whether the PQL definition is too loose.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical Rollout Plan
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 1–2: Data foundation.&lt;/strong&gt; Audit user-to-account mapping, domain rules and exclusions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 3–4: Define and back-test.&lt;/strong&gt; Build PQL and PQA definitions from historical conversions and test them against closed-won and closed-lost accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 5–6: Pilot routing.&lt;/strong&gt; Launch Tier 1 and Tier 2 alerts to a small group of AEs with SLA timers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 7–10: Review and tune.&lt;/strong&gt; Check false positives, SLA misses and rep feedback, and adjust thresholds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ongoing:&lt;/strong&gt; Re-validate definitions after pricing, packaging or product changes.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;PQLs and PQAs answer different questions. A PQL tells you a person is getting value, and a PQA tells you an organization may be ready to buy. Separating them lets you spend your fastest, most senior response on the accounts where it matters most, and lets automation handle the rest. The result is fewer wasted alerts, faster enterprise engagement and a data model that reflects how companies actually buy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://priyabhatia.substack.com/p/pqls-vs-pqas" rel="noopener noreferrer"&gt;PQLs vs. PQAs, Priya Bhatia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pocus.notion.site/How-to-analyze-usage-data-to-create-Product-Qualified-Lead-PQL-definitions-3e22c597420b4c99b8bbb39b3d8d9a4f" rel="noopener noreferrer"&gt;How to analyze usage data to create PQL definitions, Pocus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://clearbit.com/changelog/2025-02-13" rel="noopener noreferrer"&gt;Clearbit release notes: free tools discontinued&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thenextweb.com/news/apollo-acquires-pocus-revenue-intelligence-gtm" rel="noopener noreferrer"&gt;Apollo.io acquires Pocus, The Next Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.usecarly.com/blog/speed-to-lead-statistics/" rel="noopener noreferrer"&gt;Speed to Lead Statistics, Carly (on the origins of the 5-minute and 1-hour figures)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.leandata.com/blog/buying-groups-todays-b2b-go-to-market-motions" rel="noopener noreferrer"&gt;Buying groups and B2B go-to-market motions, LeanData (citing Gartner)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://usagepricing.com/blueprint/stack/leandata" rel="noopener noreferrer"&gt;LeanData overview, Usage Pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/routing-product-qualified-accounts-pqas-vs-product-qualified-leads-pqls-why-different-sla-tiers-matter" rel="noopener noreferrer"&gt;https://instachime.com/blog/routing-product-qualified-accounts-pqas-vs-product-qualified-leads-pqls-why-different-sla-tiers-matter&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Math of the Micro-SLA: Why 5 Minutes May Already Be Too Slow for High-Value Inbound Pipeline</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Mon, 19 Oct 2026 02:02:48 +0000</pubDate>
      <link>https://dev.to/instachime/the-math-of-the-micro-sla-why-5-minutes-may-already-be-too-slow-for-high-value-inbound-pipeline-54d5</link>
      <guid>https://dev.to/instachime/the-math-of-the-micro-sla-why-5-minutes-may-already-be-too-slow-for-high-value-inbound-pipeline-54d5</guid>
      <description>&lt;p&gt;Most B2B revenue teams know the rule: &lt;em&gt;respond to inbound leads quickly&lt;/em&gt;. For years the benchmark has been &lt;strong&gt;five minutes&lt;/strong&gt;, and many Revenue Operations (RevOps) playbooks give Sales Development Representatives (SDRs) or Account Executives (AEs) a window of roughly that length, sometimes longer, to claim and contact an inbound demo request.&lt;/p&gt;

&lt;p&gt;The research behind that benchmark is real, but it is also older, narrower and more commonly misquoted than most blog posts admit. This article separates what the evidence supports from what it doesn’t, then makes a practical case for a &lt;strong&gt;sub-60-second “Micro-SLA”&lt;/strong&gt; on high-value inbound requests. It also covers the routing architecture, the escalation logic and the metrics needed to make one work.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What the Research Actually Says
&lt;/h2&gt;

&lt;p&gt;Four sources do most of the work in the speed-to-lead debate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The MIT / InsideSales.com Lead Response Management study (2007).&lt;/strong&gt; Dr. James Oldroyd and InsideSales.com analysed web-generated leads from several companies. &lt;a href="https://ainora.lt/blog/lead-response-time-statistics-every-study-2026" rel="noopener noreferrer"&gt;Secondary summaries&lt;/a&gt; describe the data set as six companies, more than 15,000 leads and more than 100,000 call attempts. The headline findings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The odds of &lt;strong&gt;contacting&lt;/strong&gt; a lead were about &lt;strong&gt;100x higher&lt;/strong&gt; when the first attempt came within 5 minutes rather than 30 minutes.&lt;/li&gt;
&lt;li&gt;The odds of &lt;strong&gt;qualifying&lt;/strong&gt; a lead were about &lt;strong&gt;21x higher&lt;/strong&gt; within 5 minutes versus 30 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These multipliers compare 5 minutes to &lt;strong&gt;30 minutes&lt;/strong&gt;, not to 1 minute. They are frequently misattributed to Harvard Business Review. They also describe the odds of connecting and qualifying, not the odds of closing a deal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Harvard Business Review study (2011).&lt;/strong&gt; In &lt;a href="https://hbr.org/2011/03/the-short-life-of-online-sales-leads" rel="noopener noreferrer"&gt;“The Short Life of Online Sales Leads”&lt;/a&gt;, Oldroyd, McElheran and Elkington analysed 1.25 million leads received by 29 B2C and 13 B2B companies. Firms that tried to contact a lead within an hour were nearly &lt;strong&gt;7x&lt;/strong&gt; as likely to qualify it (defined as a meaningful conversation with a key decision maker) as firms that waited even one more hour. They were more than &lt;strong&gt;60x&lt;/strong&gt; as likely as firms that waited 24 hours or longer.&lt;/p&gt;

&lt;p&gt;The same article reported an audit of how companies actually behave. Only 37% responded to a test lead within an hour, 23% never responded at all, and the average response among those that did was 42 hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Velocify (2012).&lt;/strong&gt; Velocify’s analysis of roughly 3.5 million leads from 400+ companies reported that leads contacted within &lt;strong&gt;one minute&lt;/strong&gt; converted at a &lt;strong&gt;391% higher&lt;/strong&gt; rate. Secondary sources describe the comparison baseline inconsistently, some as waiting 2 minutes and others as 30 minutes. Treat the figure as a vendor-reported, directional signal that the first minute matters, not as a precise multiplier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The “78% buy from the first responder” claim.&lt;/strong&gt; This is one of the most widely repeated statistics in the space, usually credited to a “Lead Connect” survey. &lt;a href="https://www.usecarly.com/blog/speed-to-lead-statistics/" rel="noopener noreferrer"&gt;Independent reviews&lt;/a&gt; have not found a traceable original source or methodology. This article does not rely on it, and you shouldn’t either.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the evidence does and doesn’t show
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It supports&lt;/strong&gt; a steep, early drop-off in the odds of reaching and qualifying a lead. The sharpest documented fall is between roughly 5 and 30 minutes, and fast responders outperform slow ones by wide margins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not prove&lt;/strong&gt; that 5 minutes is “already too slow.” The best-known studies use 5 minutes as their fast benchmark. The sub-5-minute evidence rests mainly on Velocify’s one-minute finding, which is vendor data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is observational.&lt;/strong&gt; Teams that respond fast may also be better organised or better at prospecting in other ways. These are correlations from call logs, not randomised experiments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is dated and mostly phone-based.&lt;/strong&gt; The core datasets are from 2007 to 2012, and they cover a mix of B2C and B2B leads, not specifically $100k+ enterprise software buyers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reasonable conclusion is that speed is a strong lever and the curve is steepest at the start. For your highest-value leads, shaving a 5-minute SLA down to under a minute is a low-regret bet. Test it in your own funnel before assuming any published multiplier applies to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Modeling Intent Decay (Illustratively)
&lt;/h2&gt;

&lt;p&gt;To reason about the trade-offs, it helps to use a simple model. One common simplification treats the likelihood of reaching an engaged buyer as decaying exponentially:&lt;/p&gt;

&lt;p&gt;I(t)=I0⋅e−λtI(t) = I_0 \cdot e^{-\lambda t}I(t)=I0​⋅e−λt&lt;/p&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I(t)I(t)
I(t) is the probability of a live, engaged connection at time tt
t (in minutes).&lt;/li&gt;
&lt;li&gt;I0I_0
I0​ is the initial engagement state at form submission (t=0t = 0
t=0).&lt;/li&gt;
&lt;li&gt;λ\lambda
λ is a decay constant that depends on the buyer, the market and the channel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a &lt;strong&gt;modeling assumption&lt;/strong&gt;, not a published finding. Neither the MIT nor the HBR data establishes a specific functional form or a universal λ\lambda&lt;br&gt;
λ. You should estimate λ\lambda&lt;br&gt;
λ from your own data by bucketing historical leads by time-to-first-touch and measuring connect rates.&lt;/p&gt;

&lt;p&gt;To show the shape, here is what the model implies for an &lt;em&gt;arbitrary&lt;/em&gt; λ=0.2\lambda = 0.2&lt;br&gt;
λ=0.2 per minute:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Time since submission&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Remaining engagement (e−0.2te^{-0.2t} e−0.2t)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;45 seconds&lt;/td&gt;
&lt;td&gt;~86%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 minute&lt;/td&gt;
&lt;td&gt;~82%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;td&gt;~37%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 minutes&lt;/td&gt;
&lt;td&gt;~5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30 minutes&lt;/td&gt;
&lt;td&gt;~0.25%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The numbers are illustrative only. The point is structural: when decay is front-loaded, each minute of delay costs far more at the start of the window than at the end. Whatever your real curve looks like, the first few minutes are where response-time engineering pays off most.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why a 5-minute SLA can still leave money on the table
&lt;/h3&gt;

&lt;p&gt;Three plausible mechanisms explain why faster can keep helping below five minutes. These are reasoned hypotheses, not proven effects.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context and attention.&lt;/strong&gt; A buyer who has just submitted a form has the problem fresh in mind. The longer you wait, the more likely they have moved to a meeting or another task. The research quantifies the overall decay but not this specific mechanism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive timing.&lt;/strong&gt; Enterprise buyers often evaluate several vendors. A rep who connects while the buyer is still in research mode may shape the evaluation. The commonly quoted “78% buy from the first responder” figure is unsourced, but the underlying logic is reasonable and testable in your own win/loss data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expectation setting.&lt;/strong&gt; Buyers are used to instant digital experiences, and a slow first touch may signal slow service. This is a reasonable inference, not a measured effect.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  3. The Sub-60-Second Pipeline
&lt;/h2&gt;

&lt;p&gt;A sub-60-second response is an engineering and process problem, not a motivation problem. Here is a workable time budget that leaves most of the minute for the human connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; [Form Submit] ─► [Enrichment] ─► [ICP / Tiering] ─► [Route &amp;amp; Claim] ─► [Connect]
     t = 0s          t ≈ 1–3s        t ≈ 3–5s          t ≈ 5–15s       t &amp;lt; 60s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Webhook ingestion (0–2s).&lt;/strong&gt; Send the form payload directly to routing middleware by webhook, rather than waiting on a batch sync into the CRM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enrichment (≈1–3s).&lt;/strong&gt; Look up company size, industry and account match. Set a hard latency budget, such as 1.5 seconds, and fall back to form-provided fields if the call times out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiering and assignment (≈3–8s).&lt;/strong&gt; Apply territory rules, named-account ownership and rep availability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claim and connect (up to 60s).&lt;/strong&gt; Trigger the appropriate engagement channels at once:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instant scheduling&lt;/strong&gt; on the confirmation page, tied to the assigned rep’s calendar. Tools like Chili Piper are built for this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Call bridging&lt;/strong&gt;, where the system rings the rep first and then dials the buyer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live chat handoff&lt;/strong&gt; to an available rep.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Illustrative revenue math
&lt;/h3&gt;

&lt;p&gt;The following is a &lt;strong&gt;hypothetical model&lt;/strong&gt;, not a prediction. Every input is an assumption that you should replace with your own funnel data. It assumes 1,000 Tier-1 inbound demo requests per month and a $100,000 average contract value (ACV).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Scenario A: ~7.5 min average response&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Scenario B: ~45 sec average response&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Contact rate (assumed)&lt;/td&gt;
&lt;td&gt;28% → 280 contacts&lt;/td&gt;
&lt;td&gt;68% → 680 contacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qualification rate (assumed)&lt;/td&gt;
&lt;td&gt;40% → 112 opportunities&lt;/td&gt;
&lt;td&gt;55% → 374 opportunities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Win rate from opportunity (assumed)&lt;/td&gt;
&lt;td&gt;25% → 28 deals&lt;/td&gt;
&lt;td&gt;28% → ~105 deals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revenue from the monthly cohort&lt;/td&gt;
&lt;td&gt;$2.8M&lt;/td&gt;
&lt;td&gt;~$10.5M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference is roughly +$7.7M per monthly cohort, or about 3.7x. The arithmetic is internally consistent, but the underlying improvements are far larger than anything the cited studies document. The published figures are about odds of contact and qualification at different delays, not a guaranteed 2.4x jump in contact rate. A realistic way to use this model is to plug in conservative uplift assumptions and ask whether the investment still pays back. It also helps to remember that the lift applies only to the leads that were being lost to delay, and that a small share of any cohort will be unreachable at any speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Designing a Reliable Micro-SLA
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tier your leads
&lt;/h3&gt;

&lt;p&gt;Not every inbound request deserves an instant phone bridge. Use firmographic scoring to tier leads automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tier 1 (target accounts, high-value ICP):&lt;/strong&gt; a sub-60-second SLA, with call bridging, priority Slack or Teams alerts and direct calendar routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 2 (mid-market, general ICP):&lt;/strong&gt; a standard sub-5-minute SLA with round-robin routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 3 (non-ICP, low intent):&lt;/strong&gt; an asynchronous nurture sequence with no rep alert.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build a 30-second escalation protocol
&lt;/h3&gt;

&lt;p&gt;If the primary owner has not claimed a Tier-1 lead within 30 seconds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Escalate to a &lt;strong&gt;pod-wide “first-to-claim” pool&lt;/strong&gt;, with simultaneous mobile and desktop alerts to all available reps.&lt;/li&gt;
&lt;li&gt;If it is still unclaimed at 45 seconds, route it to an on-duty manager or a dedicated inbound overlay rep.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Route on live presence
&lt;/h3&gt;

&lt;p&gt;Static assignment tables send leads to reps who are in meetings or offline. Use real-time signals instead, such as calendar status, Slack presence and softphone availability, to skip unavailable reps automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌──────────────────────────┐
                 │   Inbound Lead Submitted │
                 └────────────┬─────────────┘
                              ▼
                 ┌──────────────────────────┐
                 │ Real-Time Routing Engine │
                 └────────────┬─────────────┘
                 ┌────────────┴────────────┐
                 ▼                         ▼
        [ Owner available ]       [ Owner unavailable ]
                 ▼                         ▼
        ┌─────────────────┐       ┌─────────────────┐
        │ 30s direct claim│       │ Pod first-to-   │
        │ window          │       │ claim           │
        └───────┬─────────┘       └────────┬────────┘
          claimed │ unclaimed (&amp;gt;30s)        │ unclaimed (&amp;gt;45s)
                 │        └────────►───────┤        ┌──► Manager / overlay
                 ▼                         ▼        │
        ┌─────────────────────────────────────────┐ │
        │        Instant buyer–rep connection     │◄┘
        └─────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Technical Blueprint
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   FRONTEND              MIDDLEWARE                    SYSTEMS OF RECORD
┌───────────────┐    ┌──────────────────────┐       ┌────────────────────┐
│ Website form  │───►│ Routing / scheduling │──────►│ CRM / MAP          │
│               │    │ (e.g. LeanData,      │       │ (Salesforce,       │
└───────────────┘    │ Chili Piper, custom) │       │ HubSpot)           │
                     └──────────┬───────────┘       └────────────────────┘
                                ▼
                     ┌──────────────────────┐
                     │ Real-time channels   │
                     │ (telephony, Slack)   │
                     └──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Form layer:&lt;/strong&gt; Client-side validation and bot filtering before dispatch, so junk submissions don’t consume rep attention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enrichment:&lt;/strong&gt; Choose a provider with a real-time API and a strict latency budget, such as ZoomInfo or Apollo. Clearbit is no longer a straightforward option for most teams. HubSpot acquired it in late 2023 and folded it into &lt;a href="https://clearbit.com/blog/the-future-of-clearbits-free-tools" rel="noopener noreferrer"&gt;Breeze Intelligence&lt;/a&gt;. The free Clearbit tools were discontinued on April 30, 2025, and the Logo API was scheduled to sunset on December 1, 2025. Breeze Intelligence is generally available only inside HubSpot, so verify current API access before designing around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing middleware:&lt;/strong&gt; A purpose-built tool (LeanData, Chili Piper) or custom serverless functions. Pick whichever lets you meet your latency budget and integrates with your CRM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telephony and bridging:&lt;/strong&gt; A programmable voice API such as Twilio, or a sales engagement platform such as Salesloft or Outreach, can ring the rep and then dial the buyer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Python sketch: SLA escalation engine
&lt;/h3&gt;

&lt;p&gt;The following is simplified illustrative code, not production-ready. It uses synchronous polling for readability. In production you would use event-driven or async processing, push-based claim signals, retries and proper error handling. The enrichment URL and response schema are placeholders for whichever provider you use, and the Slack message shows a simple button via Block Kit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;

&lt;span class="n"&gt;SLA_PRIMARY_CLAIM_TIMEOUT_SEC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="n"&gt;SLA_POD_ESCALATION_TIMEOUT_SEC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;
&lt;span class="n"&gt;ENRICHMENT_API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.your-enrichment-provider.example/v1/lookup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# placeholder
&lt;/span&gt;&lt;span class="n"&gt;SLACK_WEBHOOK_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://hooks.slack.com/services/XXX/YYY/ZZZ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;            &lt;span class="c1"&gt;# placeholder
&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MicroSLARouter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;crm_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;telephony_client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crm_client&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telephony&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;telephony_client&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_inbound_lead_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;enriched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_enrich_lead_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_evaluate_icp_tier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enriched&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TIER_1_ENTERPRISE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_route_asynchronous&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_active_account_owner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;claimed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rep_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_execute_claim_workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUCCESS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claimed&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNCLAIMED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lead_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assigned_rep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rep_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;execution_time_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_enrich_lead_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Real-time firmographic enrichment with a hard timeout and safe fallback.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENRICHMENT_API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;  &lt;span class="c1"&gt;# fall back to form-provided values
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_evaluate_icp_tier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;enriched&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enriched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;employees&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;employee_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;is_demo_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;form_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo_request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;is_demo_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# example threshold; tune to your ICP
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TIER_1_ENTERPRISE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STANDARD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_active_account_owner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;owner_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup_named_account_owner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;owner_id&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_rep_presence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AVAILABLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;owner_id&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_execute_claim_workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;primary_rep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
        &lt;span class="c1"&gt;# Phase 1: direct notification to the account owner (0-30s)
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;primary_rep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telephony&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trigger_rep_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_rep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_wait_for_claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_rep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SLA_PRIMARY_CLAIM_TIMEOUT_SEC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telephony&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bridge_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_rep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_rep&lt;/span&gt;

        &lt;span class="c1"&gt;# Phase 2: pod-wide first-to-claim (30-45s)
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_notify_slack_pod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enterprise lead unclaimed: pod claim open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pod_reps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_active_pod_members&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;pod_window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SLA_POD_ESCALATION_TIMEOUT_SEC&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;SLA_PRIMARY_CLAIM_TIMEOUT_SEC&lt;/span&gt;
        &lt;span class="n"&gt;claimed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_poll_pod_for_claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pod_reps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod_window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claimed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telephony&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bridge_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;winner&lt;/span&gt;

        &lt;span class="c1"&gt;# Phase 3: manager / overlay fallback (&amp;gt;45s)
&lt;/span&gt;        &lt;span class="n"&gt;manager_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_on_call_manager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;manager_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telephony&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bridge_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;manager_id&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_notify_slack_pod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;section&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;*Company:* &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;elements&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plain_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Claim &amp;amp; Connect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://revops.internal/claim?lead_id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lead_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="p"&gt;}]},&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;  &lt;span class="c1"&gt;# alert failures must not block routing
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_wait_for_claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rep_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_lead_claimed_by_rep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rep_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_poll_pod_for_claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod_reps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;claimed_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_lead_claim_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claimed_by&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pod_reps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claimed_by&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_route_asynchronous&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# Placeholder: enqueue for standard round-robin or nurture
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;QUEUED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Governance, Dashboards and Incentives
&lt;/h2&gt;

&lt;p&gt;The technology is only half the work. Micro-SLAs hold up only if measurement, staffing and incentives line up behind them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metrics to track
&lt;/h3&gt;

&lt;p&gt;The targets below are &lt;strong&gt;suggested starting points&lt;/strong&gt;, not industry benchmarks. Calibrate them to your own staffing and lead volume.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Metric&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Definition&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Suggested starting target&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Median time-to-connect&lt;/td&gt;
&lt;td&gt;Form submit to live voice or chat connection&lt;/td&gt;
&lt;td&gt;Under 45 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Micro-SLA attainment&lt;/td&gt;
&lt;td&gt;% of Tier-1 leads connected within 60 seconds&lt;/td&gt;
&lt;td&gt;Above 90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unclaimed escalation rate&lt;/td&gt;
&lt;td&gt;% of leads that pass the primary owner and go to the pod&lt;/td&gt;
&lt;td&gt;Under 5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Governance practices
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reroute without penalty.&lt;/strong&gt; If an assigned AE is legitimately in a client meeting, the system reassigns the lead without counting it against them. Apply consequences only to reps marked “Available” who repeatedly miss claim windows. For example, two misses in a day could trigger a temporary removal from the Tier-1 pool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incentive alignment.&lt;/strong&gt; Consider tying a modest inbound bonus or spiff to SLA compliance. The size of any such incentive (say, a percentage booster on deals where time-to-claim was under 45 seconds) is a design choice to test, not a market norm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekly variance analysis.&lt;/strong&gt; Compare time-to-connect against opportunity-creation rates across pods and time zones to find understaffed hours and coverage gaps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Close the loop with your own data.&lt;/strong&gt; Segment win rates by time-to-first-touch for at least a quarter before and after the change. This is the only way to know what your own uplift is, independent of any published study.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion: Speed as a Competitive Advantage
&lt;/h2&gt;

&lt;p&gt;The evidence is consistent on one point: the earliest minutes after a form submission are the most valuable, and most companies are far too slow. The best-known studies show large gaps in contact and qualification odds between 5-minute and 30-minute responses, and between one-hour and next-day responses. Industry audits suggest the typical company responds in hours or days, not minutes.&lt;/p&gt;

&lt;p&gt;For high-value enterprise inbound, a sub-60-second Micro-SLA is a sensible, testable bet. It needs real-time enrichment, presence-aware routing, a clear escalation path and incentives that reward speed. The published multipliers are directional and come mostly from older, vendor-linked datasets, so treat them as motivation to measure your own funnel, not as promises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Oldroyd, McElheran &amp;amp; Elkington, &lt;a href="https://hbr.org/2011/03/the-short-life-of-online-sales-leads" rel="noopener noreferrer"&gt;“The Short Life of Online Sales Leads”&lt;/a&gt;, &lt;em&gt;Harvard Business Review&lt;/em&gt;, March 2011.&lt;/li&gt;
&lt;li&gt;InsideSales.com / Dr. James Oldroyd, Lead Response Management Study (2007), summarised at &lt;a href="https://www.insidesales.com/lead-generation-strategies-when-to-call-when-not-to-call/" rel="noopener noreferrer"&gt;InsideSales.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Velocify lead response research (2012), as summarised in secondary sources, including &lt;a href="https://www.usecarly.com/blog/speed-to-lead-statistics/" rel="noopener noreferrer"&gt;this review of speed-to-lead statistics&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Clearbit / HubSpot, &lt;a href="https://clearbit.com/blog/the-future-of-clearbits-free-tools" rel="noopener noreferrer"&gt;“The Future of Clearbit’s Free Tools”&lt;/a&gt;, February 2025.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/the-math-of-the-micro-sla-why-5-minutes-may-already-be-too-slow-for-high-value-inbound-pipeline" rel="noopener noreferrer"&gt;https://instachime.com/blog/the-math-of-the-micro-sla-why-5-minutes-may-already-be-too-slow-for-high-value-inbound-pipeline&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Psychology of the Sub-Minute Claim: Why Tapping “Claim” Beats Reading an Email</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Sun, 18 Oct 2026 03:34:16 +0000</pubDate>
      <link>https://dev.to/instachime/the-psychology-of-the-sub-minute-claim-why-tapping-claim-beats-reading-an-email-4bdc</link>
      <guid>https://dev.to/instachime/the-psychology-of-the-sub-minute-claim-why-tapping-claim-beats-reading-an-email-4bdc</guid>
      <description>&lt;p&gt;In high-velocity sales, response time is one of the strongest predictors of whether an inbound lead ever turns into a conversation. Most companies still respond far too slowly. In a Harvard Business Review audit of 2,241 US companies, only 37% answered a web-generated test lead within an hour, and 23% never answered at all. Among the companies that did respond within 30 days, the average first response took 42 hours.&lt;/p&gt;

&lt;p&gt;Revenue operations teams have spent years speeding up the back end: instant routing, auto-responders and email alerts. The remaining delay is often human. A rep has to notice a notification, read it, decide whether to act, open the CRM and find the phone number. This article looks at why a single-tap “Claim” card, the pattern behind features like first-to-respond lead alerts, may shorten that gap. It also separates what the research supports from what it doesn’t.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What the Speed-to-Lead Research Actually Shows
&lt;/h2&gt;

&lt;p&gt;Four studies are cited most often, and they are frequently misquoted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lead Response Management Study (2007).&lt;/strong&gt; This is the source of the famous “21x” figure. It was led by Dr. James Oldroyd, then an MIT Sloan fellow, in partnership with the vendor InsideSales.com. It analyzed three years of data from six companies, covering more than 15,000 web-generated leads and over 100,000 call attempts. It found that calling within 5 minutes rather than 30 minutes was associated with roughly 100 times higher odds of making contact and 21 times higher odds of qualifying the lead. Two caveats apply. The data came from a software vendor’s platform and was presented at a 2007 industry summit rather than published in a peer-reviewed journal. It is also often mislabeled as an “MIT study” and sometimes misquoted as “5 minutes versus an hour,” when the comparison was 5 versus 30 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Harvard Business Review (2011).&lt;/strong&gt; Oldroyd, Kristina McElheran and David Elkington published “The Short Life of Online Sales Leads.” The audit figures above come from it. A separate analysis in the same article covered 1.25 million leads at 29 B2C and 13 B2B companies. Firms that contacted a lead within an hour were nearly seven times as likely to qualify it as firms that waited even one more hour, and more than 60 times as likely as firms that waited 24 hours or longer. Here, “qualifying” meant having a meaningful conversation with a key decision maker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Velocify (2012 data).&lt;/strong&gt; Velocify, now part of ICE Mortgage Technology, analyzed nearly 3.5 million leads from more than 400 companies. It reported that calling within one minute of lead generation was associated with a 391% higher conversion rate. The company also reported smaller gains for calls placed later: about 62% if called within 30 minutes and 36% within the first hour. Treat the 391% as a one-minute-versus-later comparison. It is not an extra gain on top of the 5-minute figure, as it is sometimes presented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InsideSales (2021).&lt;/strong&gt; A later InsideSales analysis, as reported by secondary sources, covered more than 55 million sales activities across 5.7 million inbound leads at over 400 companies. It reported conversion rates more than eight times higher when the first attempt came within five minutes than when it came between five minutes and 24 hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means for your claims
&lt;/h3&gt;

&lt;p&gt;All of these are observational datasets, mostly from vendors with a commercial interest in faster response. They show a strong association between speed and outcomes. Some of the effect may come from fast teams being better organized, or from hot leads being prioritized. The direction is consistent across studies and datasets, though, and the decay is steepest in the first minutes. The traditional alert-to-action path has three stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Passive notification&lt;/strong&gt;: an email, Slack message or push alert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Information processing&lt;/strong&gt;: reading, interpreting and deciding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action execution&lt;/strong&gt;: opening the CRM and dialing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A claim card compresses this into: &lt;strong&gt;see the card, tap “Claim,” start the call.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. A Caution About “4x” Claims
&lt;/h2&gt;

&lt;p&gt;You will see claims that claim cards or similar tools produce a “4x” conversion lift. We could not find independent, published evidence for a specific multiplier from the claim-card interface itself. The closest well-documented figure is Velocify’s 391% lift, which measures &lt;em&gt;how fast the first call happens&lt;/em&gt;, not which interface triggered it. A fair way to put it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calling within about a minute is associated with sharply higher conversion than calling later (Velocify).&lt;/li&gt;
&lt;li&gt;A claim-based workflow is one practical way to shorten the time to first call.&lt;/li&gt;
&lt;li&gt;The size of the benefit for your team depends on your baseline response time, lead quality and routing rules, so measure it yourself.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Cognitive Load and Friction
&lt;/h2&gt;

&lt;p&gt;Cognitive load theory comes from educational psychologist John Sweller (1988) and holds that working memory is limited. Mental effort spent on interpreting an interface is effort not available for the task itself. It was developed for learning and problem-solving, so applying it to sales notifications is an extension of the theory, not a tested result. It is still a useful lens.&lt;/p&gt;

&lt;h3&gt;
  
  
  The email-alert path
&lt;/h3&gt;

&lt;p&gt;A typical email or text-heavy alert asks the rep to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Switch context&lt;/strong&gt; from whatever they are doing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract information&lt;/strong&gt; from unstructured text, such as company, source and what the lead asked for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide&lt;/strong&gt; whether to act now or after the current task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switch apps&lt;/strong&gt; to a CRM or dialer, wait for it to load, find the record and dial.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step is small, but together they give a busy rep many natural places to defer (“I’ll call after this”).&lt;/p&gt;

&lt;h3&gt;
  
  
  The claim-card path
&lt;/h3&gt;

&lt;p&gt;A well-designed claim card shows only the information needed to decide, and one obvious action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------------------------------+
|                   NEW INBOUND LEAD                |
+---------------------------------------------------+
|  Company: Example Co. | 500 employees             |
|  Source: Requested a demo 40 seconds ago          |
+---------------------------------------------------+
|                                                   |
|                [  TAP TO CLAIM  ]                 |
|                                                   |
+---------------------------------------------------+
|           Unclaimed: passes to next rep in 0:18   |
+---------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Illustrative mockup, not data from a real product.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three design principles are at work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited, structured information.&lt;/strong&gt; The card surfaces a few key fields. This relates to Hick’s Law, an established finding that decision time grows with the number of options. A card with one primary action keeps that number low.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One action.&lt;/strong&gt; A large button maps directly to a thumb tap and avoids app switching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immediate next step.&lt;/strong&gt; The best implementations hand the rep straight into the call or message after the tap, not just a CRM update.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Behavioral Economics: Scarcity, Competition and Loss Aversion
&lt;/h2&gt;

&lt;p&gt;Claim systems add one more ingredient: urgency created by a timer and by competition from colleagues.&lt;/p&gt;

&lt;h3&gt;
  
  
  What loss aversion says
&lt;/h3&gt;

&lt;p&gt;Loss aversion, from Daniel Kahneman and Amos Tversky’s prospect theory, is the finding that losses weigh more heavily than equivalent gains. The popular “roughly twice as much” figure comes from a 1992 paper that estimated a coefficient of 2.25 from a small sample of graduate students. A 2024 meta-analysis in the &lt;em&gt;Journal of Economic Literature&lt;/em&gt; by Brown, Imai, Vieider and Camerer pooled 607 estimates from 150 articles. It put the mean coefficient at about 1.955, with a 95% interval of 1.82 to 2.10. So “about twice” is a reasonable summary, with real variation across studies and contexts.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it may apply to claiming
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assigned leads&lt;/strong&gt; go to a specific rep’s queue. Nothing is lost by waiting a few minutes, so urgency is low.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claim-based leads&lt;/strong&gt; are offered to a pool of reps, and the first to respond gets the lead. Velocify’s lead distribution documentation lists this style of routing, which it calls “Shotgun Connect,” as suited to “hot, time-sensitive internet leads that need immediate response.” PhoneBurner’s “Lead Blitz” works the same way, alerting reps in real time so the first to claim gets the call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One caveat on the theory: before claiming, the rep doesn’t yet own the lead. The motivating force is therefore better described as &lt;strong&gt;scarcity and competition&lt;/strong&gt;, with loss aversion a plausible contributor if reps mentally treat an offered lead as theirs. We found no published study that isolates loss aversion in claim workflows, so this is a reasoned application, not a measured result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fairness and side effects
&lt;/h3&gt;

&lt;p&gt;First-to-claim systems can reward whoever is nearest their phone and may let some reps cherry-pick the best leads. Teams often add guardrails such as capacity limits, escalation to a fallback owner when a lead goes unclaimed, and per-rep tracking of claim times. One brokerage case study published by an automation consultancy describes a Zoho Cliq claim button with an unclaimed-lead timer that escalates to a fallback owner, and with claim time and claimant recorded so speed to lead can be measured per agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Email Alerts vs. Claim Cards: A Qualitative Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Traditional email/CRM workflow&lt;/th&gt;
&lt;th&gt;Mobile claim-card workflow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alert delivery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Email or chat message that can sit unread&lt;/td&gt;
&lt;td&gt;Push notification that opens a focused card&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Information&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rep reads text and hunts for context&lt;/td&gt;
&lt;td&gt;Key fields pre-selected and displayed together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Urgency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Little pressure; lead waits in the queue&lt;/td&gt;
&lt;td&gt;Visible timer and competition from teammates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CRM access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rep opens a separate app or browser and finds the record&lt;/td&gt;
&lt;td&gt;Ownership assigned automatically when the rep taps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Action&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rep copies or looks up a number and dials&lt;/td&gt;
&lt;td&gt;Tap can open a call or message directly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Actual time savings vary by team, so measure them instead of relying on published averages. Useful metrics are time from form submission to claim, time from claim to first call attempt, and connect and qualification rates by response-time bucket.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Designing a Good Claim Card
&lt;/h2&gt;

&lt;p&gt;For RevOps and sales enablement teams, a few practical guidelines follow from the above.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Show the minimum needed to decide.&lt;/strong&gt; Typically this is who the lead is, a value or fit signal such as company size or score, and the intent action, such as a pricing request versus a content download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the timer realistic.&lt;/strong&gt; Windows of tens of seconds to a few minutes are common. Whatever you choose, pair it with an escalation path, such as the next rep or a manager, so no lead is left unclaimed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protect fairness.&lt;/strong&gt; Use availability or capacity checks, caps on concurrent leads, and reporting on who claims what.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect the tap to the action.&lt;/strong&gt; Claiming should assign the lead and also launch the call, SMS or dialer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure.&lt;/strong&gt; Track claim time, first-attempt time and downstream qualification so you know whether the change helped in your own data.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The evidence that fast response matters is strong and consistent, though mostly observational and vendor-sourced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calling within five minutes rather than 30 was associated with about 21 times higher odds of qualification in the 2007 Lead Response Management Study.&lt;/li&gt;
&lt;li&gt;Contacting a lead within an hour made qualification nearly seven times likelier than waiting even an hour longer, in the HBR analysis of 1.25 million leads.&lt;/li&gt;
&lt;li&gt;Calling within a minute was associated with a 391% higher conversion rate in Velocify’s data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The case for claim cards rests on a plausible, theory-based argument: they reduce friction, shorten the path from alert to call and add healthy urgency. It is not yet backed by a published multiplier. If you adopt the pattern, treat it as a hypothesis to test. Measure your time to first contact and conversion before and after, and keep fairness safeguards in place.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Oldroyd, J., McElheran, K., &amp;amp; Elkington, D. (2011). “The Short Life of Online Sales Leads.” &lt;em&gt;Harvard Business Review&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;InsideSales.com / James Oldroyd, Lead Response Management Study (2007).&lt;/li&gt;
&lt;li&gt;Velocify (ICE Mortgage Technology), lead response research (2012 data) and lead distribution documentation: &lt;a href="https://help.icemortgagetechnology.com/documentation/velocify/Content/Velocify/features/LeadManager/dist-pgm-options.htm" rel="noopener noreferrer"&gt;https://help.icemortgagetechnology.com/documentation/velocify/Content/Velocify/features/LeadManager/dist-pgm-options.htm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tversky, A., &amp;amp; Kahneman, D. (1992). “Advances in Prospect Theory: Cumulative Representation of Uncertainty.” &lt;em&gt;Journal of Risk and Uncertainty&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Brown, A. L., Imai, T., Vieider, F. M., &amp;amp; Camerer, C. F. (2024). “Meta-analysis of Empirical Estimates of Loss Aversion.” &lt;em&gt;Journal of Economic Literature&lt;/em&gt;, 62(2), 485–516: &lt;a href="https://authors.library.caltech.edu/records/stxh9-77874" rel="noopener noreferrer"&gt;https://authors.library.caltech.edu/records/stxh9-77874&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sweller, J. (1988). “Cognitive Load During Problem Solving: Effects on Learning.” &lt;em&gt;Cognitive Science&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;PhoneBurner, LeadStream / Lead Blitz documentation: &lt;a href="https://develop.phoneburner.com/lead-distribution" rel="noopener noreferrer"&gt;https://develop.phoneburner.com/lead-distribution&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/the-psychology-of-the-sub-minute-claim-why-tapping-claim-beats-reading-an-email" rel="noopener noreferrer"&gt;https://instachime.com/blog/the-psychology-of-the-sub-minute-claim-why-tapping-claim-beats-reading-an-email&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
    </item>
    <item>
      <title>Routing Inbound Enterprise Leads via WhatsApp Business API vs. SMS: Which Delivers Higher SLA Compliance?</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Sat, 17 Oct 2026 01:48:03 +0000</pubDate>
      <link>https://dev.to/instachime/routing-inbound-enterprise-leads-via-whatsapp-business-api-vs-sms-which-delivers-higher-sla-15g7</link>
      <guid>https://dev.to/instachime/routing-inbound-enterprise-leads-via-whatsapp-business-api-vs-sms-which-delivers-higher-sla-15g7</guid>
      <description>&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;

&lt;p&gt;For enterprise sales teams, speed-to-lead is a direct revenue lever. The most-cited research is the Lead Response Management study by Dr. James Oldroyd and InsideSales.com. It analysed more than 15,000 web-generated leads and over 100,000 call attempts across six companies. It found that &lt;a href="https://telecrm.in/blog/lead-response-time/" rel="noopener noreferrer"&gt;calling within five minutes rather than 30 minutes gave 100 times higher odds of making contact and 21 times higher odds of qualifying the lead&lt;/a&gt;. A separate Harvard Business Review analysis found that &lt;a href="https://sia.hackernoon.com/responding-to-leads-are-you-moving-fast-enough-2facfa8b75f3" rel="noopener noreferrer"&gt;companies contacting leads within an hour were nearly seven times more likely to have meaningful conversations with decision makers than those waiting just an hour longer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two caveats apply. These studies date from the 2000s–2010s, and they measured how quickly a &lt;em&gt;prospect&lt;/em&gt; was contacted, mostly by phone. They did not measure how a &lt;em&gt;sales rep&lt;/em&gt; was notified. The figure often quoted as “900% higher conversion at five minutes” is not traceable to these primary sources, so this article uses the published ratios instead.&lt;/p&gt;

&lt;p&gt;Meeting a sub-five-minute first-touch SLA therefore depends on how reliably and visibly your routing system alerts the right rep. This guide compares two alerting channels, the &lt;strong&gt;WhatsApp Business Platform (Cloud API)&lt;/strong&gt; and &lt;strong&gt;A2P (application-to-person) SMS&lt;/strong&gt;, on delivery telemetry, regulatory filtering, interactivity, cost structure, and the practical design of an SLA-driven routing engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; WhatsApp has clear structural advantages for SLA tracking: richer status events, interactive buttons, and no sender-registration regime. We found no independent, published head-to-head study showing a specific SLA compliance percentage for either channel. Vendor claims of precise open rates and compliance gaps should be treated with caution. The most defensible design uses WhatsApp as the primary alert, with a timed escalation to SMS and a voice call.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Enterprise SLA Challenge in High-Ticket Sales
&lt;/h2&gt;

&lt;p&gt;High-ticket sales (enterprise SaaS, commercial real estate, industrial equipment, wealth management) typically involve fewer leads but high deal values. When a high-intent prospect submits a demo or pricing request, the routing system has to move through several steps almost instantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Inbound Lead Webhook]
         │
         ▼
[CRM / Lead Routing Engine] ──(round-robin / territory logic)
         │
         ▼
[Notification Gateway] ──(WhatsApp Cloud API or A2P SMS)
         │
         ▼
[Sales Rep Mobile Device]
         │
         ▼
[First Touch] ──(call, message, or meeting booking inside the SLA window)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common SLA Failure Points
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Filtered or blocked messages.&lt;/strong&gt; SMS is subject to carrier and regulatory filtering. In India, commercial SMS must pass a three-layer check on a TRAI-approved DLT platform covering &lt;a href="https://www.telerivet.com/blog/india-sms-compliance-trai-dlt-registration-and-tcccpr-guide" rel="noopener noreferrer"&gt;entity, sender header and exact message template&lt;/a&gt;, and a message that fails any check is blocked. In the US, carriers &lt;a href="https://justcall.io/blog/10dlc-compliance-guide.html" rel="noopener noreferrer"&gt;began blocking unregistered A2P 10DLC traffic&lt;/a&gt; in the 2024–2025 period, so brand and campaign registration with The Campaign Registry is effectively mandatory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak delivery telemetry.&lt;/strong&gt; SMS delivery receipts are not guaranteed. &lt;a href="https://developer.vonage.com/messaging/sms/building-blocks/receive-a-delivery-receipt" rel="noopener noreferrer"&gt;Carriers may not return them, and a receipt may reflect only a carrier-level stage rather than handset delivery&lt;/a&gt;. A missing receipt does not prove failure, and a “delivered” receipt does not prove anyone saw the message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inbox noise.&lt;/strong&gt; Alerts compete with one-time passcodes and other automated texts. This is a plausible friction point rather than a measured one, so test it with your own reps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context-switching friction.&lt;/strong&gt; A plain-text SMS usually requires the rep to open a link or switch to the CRM before acting.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Channel Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;WhatsApp Business Platform (Cloud API)&lt;/th&gt;
&lt;th&gt;A2P SMS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delivery statuses&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sent&lt;/code&gt;, &lt;code&gt;delivered&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt; via webhooks&lt;/td&gt;
&lt;td&gt;Carrier delivery receipts (DLRs), not guaranteed by all carriers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Read confirmation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, but only when the recipient has read receipts enabled&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Regulatory/registration overhead&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Business verification and Meta-approved message templates&lt;/td&gt;
&lt;td&gt;DLT in India; 10DLC brand/campaign registration in the US; varies by country&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rich content&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Interactive templates with buttons; images, documents and contacts inside an open session window&lt;/td&gt;
&lt;td&gt;Plain text, 160 GSM-7 characters per segment (70 for Unicode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Two-way interaction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rep taps a button or replies; reply opens a 24-hour window&lt;/td&gt;
&lt;td&gt;Requires two-way number provisioning and inbound parsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connectivity needed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mobile data or Wi-Fi&lt;/td&gt;
&lt;td&gt;Cellular signal only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Per delivered template message, by category and country&lt;/td&gt;
&lt;td&gt;Per message segment, by country and route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Verified open-rate data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Read receipts available (see caveat above)&lt;/td&gt;
&lt;td&gt;Not measurable by the sender&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;About open rates: vendor pages routinely cite a “98% SMS open rate”, but one benchmark review calls it &lt;a href="https://www.clickminded.com/sms-marketing-statistics/" rel="noopener noreferrer"&gt;an inferred reach figure that is recycled across vendor pages&lt;/a&gt;, not a measured one. Likewise, vendor figures for WhatsApp open rates are not independently verified. Neither channel has a trustworthy public open-rate benchmark for rep alerting, so this guide does not rely on one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detailed Channel Analysis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Delivery Architecture and Telemetry
&lt;/h3&gt;

&lt;h4&gt;
  
  
  A2P SMS
&lt;/h4&gt;

&lt;p&gt;A typical SMS alert travels from the CRM through an SMS provider (such as Twilio, Infobip, Sinch, or Vonage) and an aggregator to the destination carrier, then to the handset:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CRM → REST API → SMS provider → aggregator → mobile network operator → handset&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deliverability depends on registration.&lt;/strong&gt; Template mismatches under India’s DLT rules lead to blocked messages. Unregistered 10DLC traffic in the US is subject to blocking and fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receipts are partial.&lt;/strong&gt; The &lt;code&gt;DELIVRD&lt;/code&gt; status means the downstream carrier reported delivery. Depending on carrier and number type, &lt;a href="https://www.bandwidth.com/support/en/articles/12823161" rel="noopener noreferrer"&gt;that may be a carrier-level or handset-level confirmation&lt;/a&gt;. Receipts can also expire without ever arriving. Either way, a receipt says nothing about whether the rep saw the message.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  WhatsApp Business Platform
&lt;/h4&gt;

&lt;p&gt;Since Meta retired the On-Premises API (its &lt;a href="https://developers.facebook.com/docs/whatsapp/on-premises/sunset" rel="noopener noreferrer"&gt;final version expired on October 23, 2025&lt;/a&gt;), the &lt;strong&gt;Cloud API&lt;/strong&gt; is the only supported way to integrate. Meta states that it targets 99.9% uptime and under 5 seconds p99 latency, and that it supports up to 1,000 messages per second. These are Meta’s own figures, not independent measurements.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CRM → REST API → Meta Cloud API → (internet) → WhatsApp client on the rep's phone&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Status webhooks.&lt;/strong&gt; Meta documents &lt;a href="https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/messages/status" rel="noopener noreferrer"&gt;sent, delivered, read and failed events&lt;/a&gt;, each with a timestamp. &lt;code&gt;read&lt;/code&gt; means the message was displayed in an open chat thread. If a user receives a message while already in the chat, Meta may send only &lt;code&gt;read&lt;/code&gt; and skip &lt;code&gt;delivered&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read receipts can be disabled.&lt;/strong&gt; If the rep has turned off read receipts, &lt;a href="https://docs.ycloud.com/en/api-reference/guides/examples/webhook-examples/whatsapp-message-updated-webhook-examples.md" rel="noopener noreferrer"&gt;you will not receive &lt;code&gt;read&lt;/code&gt; events&lt;/a&gt;. For this reason, SLA timers should key off an explicit &lt;strong&gt;button tap or reply&lt;/strong&gt; rather than the &lt;code&gt;read&lt;/code&gt; status.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template categories matter.&lt;/strong&gt; Per-user marketing limits can cause Meta to withhold a message with error &lt;code&gt;131049&lt;/code&gt;, but &lt;a href="https://www.wati.io/en/blog/whatsapp-error-131049/" rel="noopener noreferrer"&gt;these limits apply only to Marketing-category templates&lt;/a&gt;. Utility templates are not subject to them. Meta, not you, decides a template’s final category based on its content. Meta describes utility messages as &lt;a href="https://whatsappbusiness.com/products/platform-pricing/" rel="noopener noreferrer"&gt;typically triggered by a user action and specific to or requested by that user&lt;/a&gt;. An internal “new lead assigned” alert to a rep may not always qualify as Utility, so submit it for approval early and plan for the possibility of reclassification.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Rep Experience and Interactivity
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;SMS.&lt;/strong&gt; The rep must read the text, tap a short link, open a browser or app, authenticate, and find the lead record before acting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WhatsApp.&lt;/strong&gt; Templates can carry buttons directly in the message. Per Meta’s documentation, a template can have &lt;a href="https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/components.md" rel="noopener noreferrer"&gt;up to 10 buttons in total, with limits per button type&lt;/a&gt;. When there are more than three, WhatsApp shows two and collapses the rest behind a “See all options” control. Quick-reply buttons send a predefined response back to your webhook. This supports actions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claim Lead&lt;/strong&gt;: a quick reply that triggers an atomic assignment lock in the CRM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pass / Reassign&lt;/strong&gt;: a quick reply that fires a re-routing rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Lead&lt;/strong&gt;: a URL button, which can include a dynamic suffix, pointing to the lead record in your mobile CRM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that a template’s &lt;strong&gt;phone-number button dials a fixed number set when the template is created&lt;/strong&gt;, so it cannot dial a different prospect for each lead. To enable click-to-call, put the prospect’s number in the message body or link to a CRM page that offers a one-tap call.&lt;/p&gt;

&lt;p&gt;Once the rep replies or taps a quick-reply button, a 24-hour customer service window opens, and non-template messages become possible. At that point you can send richer content such as documents, images or contact cards without sending another template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notification behaviour.&lt;/strong&gt; Whether an alert interrupts the rep depends on each rep’s phone settings, such as notification permissions, Focus or Do Not Disturb modes, and per-app configuration. This applies equally to WhatsApp and SMS, and an app cannot override it. Plan for reps with silenced phones by adding an escalation step that does not rely on the same notification path, such as an automated voice call.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Measuring SLA Compliance
&lt;/h3&gt;

&lt;p&gt;Sales operations teams typically track three timestamps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Speed to Notification:&lt;/strong&gt; lead creation to alert delivery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed to Acknowledgment:&lt;/strong&gt; alert delivery to claim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed to First Touch:&lt;/strong&gt; claim to the first call, message or meeting invite sent to the prospect.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Interactive buttons make the second metric directly measurable on WhatsApp, with the rep’s tap serving as an explicit acknowledgment. With SMS, you have to infer acknowledgment from link clicks or CRM activity, which is slower to build and noisier.&lt;/p&gt;

&lt;p&gt;We did not find a credible public study quantifying SLA compliance for WhatsApp versus SMS rep alerts. Any specific figures, such as “94% vs. 48% compliance”, should be treated as unverified unless they come from your own instrumentation. The right way to answer this question for your organisation is an A/B routing test. Randomly assign reps (or leads) to each channel for a few weeks and compare acknowledgment and first-touch times from your CRM data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation: WhatsApp Lead Routing Engine (Node.js/Express)
&lt;/h2&gt;

&lt;p&gt;The example below receives a lead webhook, sends a WhatsApp template alert to the assigned rep, and handles Meta’s webhooks for delivery statuses and button taps. It is a sketch to adapt. It assumes you have an approved template named &lt;code&gt;urgent_lead_sla_alert&lt;/code&gt; with four body variables and two quick-reply buttons, and that CRM endpoints and authentication are placeholders to replace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Keep the raw body so we can verify Meta's webhook signature&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;WHATSAPP_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PHONE_NUMBER_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;WHATSAPP_APP_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;WEBHOOK_VERIFY_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;CRM_API_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GRAPH_API_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v24.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1) Inbound lead from your CRM or form handler&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/v1/lead-routing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;receivedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dealValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contactName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contactPhone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;assignedRepPhone&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;assignedRepPhone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid lead payload.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendLeadAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assignedRepPhone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dealValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contactName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contactPhone&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messageId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;wa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Record when the lead arrived and when the alert was dispatched&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CRM_API_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sla-log`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ALERT_DISPATCHED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WHATSAPP&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;leadReceivedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;receivedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;dispatchedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SLA log failed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// logging must not block routing&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messageId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Routing error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lead routing dispatch failed.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendLeadAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repPhone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://graph.facebook.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GRAPH_API_VERSION&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PHONE_NUMBER_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/messages`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;messaging_product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whatsapp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repPhone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// E.164 format&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;urgent_lead_sla_alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en_US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`$&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dealValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contactName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contactPhone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sub_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quick_reply&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`CLAIM:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sub_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quick_reply&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`PASS:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;WHATSAPP_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 2) Webhook verification handshake (Meta calls this once when you register the URL)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/whatsapp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hub.mode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subscribe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hub.verify_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;WEBHOOK_VERIFY_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hub.challenge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 3) Status updates and button taps&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/whatsapp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Verify the X-Hub-Signature-256 header&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Hub-Signature-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WHATSAPP_APP_SECRET&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// acknowledge fast, process after&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statuses&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// sent | delivered | read | failed. 'read' may never arrive if read receipts are off.&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CRM_API_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sla-log`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`WA_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// quick-reply tap on a template&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Your CRM should apply the claim atomically (compare-and-set) so two reps cannot both win&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CRM_API_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/leads/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;repPhone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Lead routing engine listening on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Cost and Value Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Messaging Cost
&lt;/h3&gt;

&lt;p&gt;WhatsApp is billed per delivered template message, and rates depend on the template category and the recipient’s country calling code. According to &lt;a href="https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing" rel="noopener noreferrer"&gt;Meta’s pricing documentation&lt;/a&gt;, utility templates delivered inside an open customer service window are free, and utility templates sent outside a window are charged. Volume tiers lower utility rates as monthly volume grows. Meta may change rates only on the first day of each quarter, so check the current rate card before budgeting. SMS pricing varies by country, route and message length, and in the US can include carrier fees for registered campaigns.&lt;/p&gt;

&lt;p&gt;For a team sending a few thousand rep alerts a month, messaging cost is typically small next to the value of a single enterprise deal. The right comparison is therefore the cost of a missed SLA, not the per-message price.&lt;/p&gt;

&lt;h3&gt;
  
  
  Illustrative Value Model
&lt;/h3&gt;

&lt;p&gt;The figures below are &lt;strong&gt;hypothetical assumptions, not benchmarks&lt;/strong&gt;. Replace them with your own CRM data.&lt;/p&gt;

&lt;p&gt;Assume 1,000 inbound leads per month, an average deal value of $25,000, a 30% lead-to-opportunity rate when contacted within five minutes, and 3% when contacted after 30 minutes. Each lead moved from the slow group to the fast group then adds 0.27 opportunities.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Improvement in SLA compliance&lt;/th&gt;
&lt;th&gt;Leads moved to fast group&lt;/th&gt;
&lt;th&gt;Extra opportunities&lt;/th&gt;
&lt;th&gt;Extra pipeline value (at $25k)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;+10 percentage points&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;$675,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+20 percentage points&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;$1,350,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+40 percentage points&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;108&lt;/td&gt;
&lt;td&gt;$2,700,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;“Pipeline value” is not revenue. Actual revenue depends on win rate, sales cycle and deal-size variance. The takeaway is a sensitivity: each additional 10 points of SLA compliance is worth far more than the messaging cost on either channel. Whichever channel you choose, the real question is how many points of compliance it buys you in your own environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recommended Architecture: WhatsApp-First with Escalation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Inbound lead webhook]
          │
          ▼
 [Primary alert] ──► WhatsApp interactive template (Claim / Pass / Open Lead)
          │
          ├─► Rep taps "Claim" within the target window ──► lock assignment, log timestamp
          │
          └─► No claim by threshold (for example, 2–3 minutes)
                    │
                    ▼
          [Escalation]
                    ├─► SMS alert (registered sender: DLT in India, 10DLC in the US)
                    ├─► Automated voice call to the rep
                    └─► Re-route to the next rep or a backup pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best Practices
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Submit utility templates early and monitor categorisation.&lt;/strong&gt; Meta decides the final category. Keep alert copy factual and free of promotional language, and check the category assigned after approval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key SLA timers to claim actions, not &lt;code&gt;read&lt;/code&gt; events.&lt;/strong&gt; Read receipts can be disabled, and a &lt;code&gt;read&lt;/code&gt; status does not prove the rep acted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep SMS as an escalation path, not an afterthought.&lt;/strong&gt; WhatsApp needs a data connection, while SMS works on cellular signal alone. Register your sender properly (DLT in India, 10DLC in the US) before you need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a non-notification escalation.&lt;/strong&gt; An automated voice call reaches reps whose phones are silenced for app notifications, and calls are the contact method that the speed-to-lead research actually measured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle consent and compliance.&lt;/strong&gt; WhatsApp requires that recipients have opted in to receive business messages, and that applies to reps as well as prospects. For prospects, capture explicit consent on the web form. Review local rules, including TRAI regulations in India and TCPA/carrier rules in the US, with your legal team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure, don’t assume.&lt;/strong&gt; Run an A/B test, log every timestamp (lead received, alert dispatched, delivered, claimed, first touch), and compare channels on your own data.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;For enterprise lead routing, WhatsApp Business Platform has real structural advantages over SMS. It offers richer status events, interactive claim-and-reassign buttons that make acknowledgment measurable, and no sender-registration filtering layer like DLT or 10DLC. Those features make a tight, auditable sub-five-minute SLA easier to engineer.&lt;/p&gt;

&lt;p&gt;The evidence for a specific compliance gap, however, is thin. There is no independent public benchmark comparing the two channels on rep SLA compliance, and the commonly quoted open-rate and response-time numbers do not hold up to scrutiny. WhatsApp also has real limitations. It needs data connectivity, &lt;code&gt;read&lt;/code&gt; receipts can be disabled, and template categorisation is controlled by Meta.&lt;/p&gt;

&lt;p&gt;The most reliable design combines WhatsApp as the primary alert, SMS and voice as timed fallbacks, and your own A/B data as the final judge.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Lead response research: &lt;a href="https://telecrm.in/blog/lead-response-time/" rel="noopener noreferrer"&gt;TeleCRM summary of the Oldroyd/InsideSales study and HBR analysis&lt;/a&gt;; &lt;a href="https://customerthink.com/?p=91555" rel="noopener noreferrer"&gt;CustomerThink on the InsideSales studies&lt;/a&gt;; &lt;a href="https://sia.hackernoon.com/responding-to-leads-are-you-moving-fast-enough-2facfa8b75f3" rel="noopener noreferrer"&gt;HackerNoon on HBR’s one-hour finding&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Meta documentation: &lt;a href="https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing" rel="noopener noreferrer"&gt;WhatsApp Business Platform pricing&lt;/a&gt;; &lt;a href="https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/messages/status" rel="noopener noreferrer"&gt;status webhook reference&lt;/a&gt;; &lt;a href="https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/components.md" rel="noopener noreferrer"&gt;template components&lt;/a&gt;; &lt;a href="https://developers.facebook.com/docs/whatsapp/on-premises/sunset" rel="noopener noreferrer"&gt;On-Premises API sunset&lt;/a&gt;; &lt;a href="https://whatsappbusiness.com/products/platform-pricing/" rel="noopener noreferrer"&gt;platform pricing overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SMS regulation and telemetry: &lt;a href="https://www.telerivet.com/blog/india-sms-compliance-trai-dlt-registration-and-tcccpr-guide" rel="noopener noreferrer"&gt;Telerivet on India’s TRAI DLT regime&lt;/a&gt;; &lt;a href="https://developer.vonage.com/messaging/sms/building-blocks/receive-a-delivery-receipt" rel="noopener noreferrer"&gt;Vonage on delivery receipts&lt;/a&gt;; &lt;a href="https://www.bandwidth.com/support/en/articles/12823161" rel="noopener noreferrer"&gt;Bandwidth on DLRs&lt;/a&gt;; &lt;a href="https://justcall.io/blog/10dlc-compliance-guide.html" rel="noopener noreferrer"&gt;JustCall on 10DLC enforcement&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Other: &lt;a href="https://www.wati.io/en/blog/whatsapp-error-131049/" rel="noopener noreferrer"&gt;Wati on error 131049&lt;/a&gt;; &lt;a href="https://www.clickminded.com/sms-marketing-statistics/" rel="noopener noreferrer"&gt;ClickMinded on SMS open-rate claims&lt;/a&gt;; &lt;a href="https://docs.ycloud.com/en/api-reference/guides/examples/webhook-examples/whatsapp-message-updated-webhook-examples.md" rel="noopener noreferrer"&gt;YCloud on read-receipt behaviour&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/routing-inbound-enterprise-leads-via-whatsapp-business-api-vs-sms-which-delivers-higher-sla-compliance" rel="noopener noreferrer"&gt;https://instachime.com/blog/routing-inbound-enterprise-leads-via-whatsapp-business-api-vs-sms-which-delivers-higher-sla-compliance&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Routing DeepSeek &amp; Claude Coding Assistant Escalations Directly to Solutions Engineers in Slack</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Fri, 16 Oct 2026 01:44:29 +0000</pubDate>
      <link>https://dev.to/instachime/routing-deepseek-claude-coding-assistant-escalations-directly-to-solutions-engineers-in-slack-15j9</link>
      <guid>https://dev.to/instachime/routing-deepseek-claude-coding-assistant-escalations-directly-to-solutions-engineers-in-slack-15j9</guid>
      <description>&lt;p&gt;Self-serve onboarding is only half the battle in developer-facing Product-Led Growth (PLG). AI coding assistants such as DeepSeek’s reasoning models and Anthropic’s Claude models handle boilerplate generation and a lot of debugging. They still hit a ceiling when an enterprise prospect runs into deep architectural constraints, custom network topologies, or SDK state-management bugs.&lt;/p&gt;

&lt;p&gt;When an in-app assistant can’t unblock a high-intent developer, a generic support queue adds friction and loses context. This guide describes an architecture for routing those escalations into private Slack channels for Solutions Engineers (SEs). Each escalation carries a context payload and an interactive “claim” card.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on model names.&lt;/strong&gt; This pattern was originally framed around &lt;strong&gt;DeepSeek-R1&lt;/strong&gt; and &lt;strong&gt;Claude 3.5 Sonnet&lt;/strong&gt;. Both are now legacy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic retired both Claude 3.5 Sonnet snapshots (&lt;code&gt;claude-3-5-sonnet-20240620&lt;/code&gt; and &lt;code&gt;claude-3-5-sonnet-20241022&lt;/code&gt;) on October 28, 2025. Its deprecations page lists &lt;code&gt;claude-sonnet-4-6&lt;/code&gt; as the recommended replacement.&lt;/li&gt;
&lt;li&gt;DeepSeek’s &lt;code&gt;deepseek-reasoner&lt;/code&gt; alias, which pointed to R1-series models earlier, was retired on July 24, 2026, along with &lt;code&gt;deepseek-chat&lt;/code&gt;. Both aliases had pointed at DeepSeek-V4-Flash since April 24, 2026.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything below works with any model that exposes a reasoning or thinking trace. Current model names are called out where it matters.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The PLG Dilemma: When AI Coding Assistants Need Human Backup
&lt;/h2&gt;

&lt;p&gt;Product-led growth depends on fast time-to-value, and developers expect tools to work out of the box. As a product moves up-market, integration complexity grows. Enterprise buyers bring security reviews, private networking, compliance requirements, and legacy systems.&lt;/p&gt;

&lt;p&gt;Imagine a prospective enterprise team evaluating your API through an in-app assistant powered by a Claude or DeepSeek model. A developer might ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;“How do we configure mutual TLS between our gateway and your webhook endpoints, given our internal certificate authority?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“Streaming responses drop intermittently when they pass through our internal gRPC proxy. What should we check?”&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions share a few traits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They depend on the prospect’s environment.&lt;/strong&gt; A model can’t inspect a private VPC or a corporate proxy configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They may involve account-level actions.&lt;/strong&gt; Provisioning an enterprise sandbox, raising limits, or approving a security exception needs a human with the right permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They carry commercial weight.&lt;/strong&gt; The person asking is often an evaluator whose experience shapes a purchasing decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Response Speed Matters in Technical Sales
&lt;/h3&gt;

&lt;p&gt;A developer who hits a blocker and then waits a day for an email reply loses momentum. A Slack-based handoff can shorten that wait, because the escalation lands where your SEs already work. Measure the effect rather than assuming it. The metrics section below describes what to track.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architectural Overview: From LLM to Slack SE Queue
&lt;/h2&gt;

&lt;p&gt;An effective escalation pipeline captures the interaction context, packages the model’s reasoning, and delivers an actionable card to Slack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Developer in IDE / Web App]
         │
         ▼
[AI Coding Assistant (Claude / DeepSeek)]
         │
   (Escalation threshold met / user clicks "Ask an SE")
         │
         ▼
[Backend Orchestration Service (Node.js / Python)]
         │
         ├──► Extracts chat history &amp;amp; reasoning trace
         ├──► Enriches with CRM data (Salesforce / HubSpot)
         │
         ▼
[Slack chat.postMessage + Block Kit] ──► [#se-escalations channel]
                                                │
                                        (SE clicks "Claim")
                                                │
                                                ▼
[Interactivity endpoint (block_actions)]
         │
         ├──► Atomically locks the escalation to the SE
         ├──► Updates the Slack card
         └──► Opens a thread / handoff channel and notifies the prospect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Components
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration layer.&lt;/strong&gt; Captures the trigger from your UI or IDE extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context enrichment.&lt;/strong&gt; Gathers the user’s organization, plan tier, SDK version, and recent error logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model trace aggregator.&lt;/strong&gt; Collects the model’s reasoning output, when available, so the SE can see where the assistant went wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack Block Kit renderer.&lt;/strong&gt; Builds an interactive card with a claim button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State store.&lt;/strong&gt; Records which SE owns the escalation, with an atomic write so two SEs can’t claim the same one.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What “Reasoning Trace” Means Per Provider
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek.&lt;/strong&gt; The V4 models (&lt;code&gt;deepseek-v4-flash&lt;/code&gt; and &lt;code&gt;deepseek-v4-pro&lt;/code&gt;) support thinking and non-thinking modes and a 1M-token context window. Thinking mode is enabled by default, and the API returns chain-of-thought text in a &lt;code&gt;reasoning_content&lt;/code&gt; field next to &lt;code&gt;content&lt;/code&gt;. DeepSeek’s change log dated August 13, 2026 added three thinking effort levels (low, high, max). Check DeepSeek’s thinking-mode guide for how to handle &lt;code&gt;reasoning_content&lt;/code&gt; across turns, especially in tool-calling workflows. Older docs for the retired &lt;code&gt;deepseek-reasoner&lt;/code&gt; said that sending it back in input messages caused a 400 error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude.&lt;/strong&gt; Claude models can return extended-thinking content blocks. On recent models these may be summarized rather than raw, so treat them as a useful hint for the SE, not a verbatim transcript.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whichever provider you use, store the trace separately from the user-visible answer and link to it from Slack instead of pasting it in. Slack’s per-element text limits are small (see Step 3).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Defining the Escalation Triggers
&lt;/h2&gt;

&lt;p&gt;Don’t escalate every query. Define explicit, auditable triggers.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Explicit User Request
&lt;/h3&gt;

&lt;p&gt;Put an “Ask a Solutions Engineer” button in the assistant UI. It gives developers an escape hatch when the assistant keeps proposing fixes that don’t fit their architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Frustration and Repetition Signals
&lt;/h3&gt;

&lt;p&gt;Watch for repeated failures in follow-up messages, such as “that didn’t work,” the same stack trace pasted again, or a restated constraint the assistant keeps ignoring. A simple heuristic, such as N consecutive failed attempts on the same topic, is a good start. Add a classifier only if the heuristic proves insufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Reasoning and Confidence Signals
&lt;/h3&gt;

&lt;p&gt;Possible automated signals include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A thinking block that grows past a token budget you set without producing a final code block.&lt;/li&gt;
&lt;li&gt;The assistant explicitly saying it lacks information about the customer’s environment.&lt;/li&gt;
&lt;li&gt;Repeated tool-call failures in an agentic coding flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are heuristics, not guarantees. Calibrate thresholds against escalations your SEs later judge useful or unnecessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Account-Based Rules
&lt;/h3&gt;

&lt;p&gt;Route by account tier. An enterprise-tier or high-fit account might escalate after fewer failed attempts than a free-tier user.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Structuring the Context Payload
&lt;/h2&gt;

&lt;p&gt;Raw chat logs pasted into Slack aren’t helpful. Build a structured payload with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prospect metadata:&lt;/strong&gt; company, plan tier, seat or usage estimates from your CRM, and SDK version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Problem summary:&lt;/strong&gt; two sentences from a lightweight model describing the blocker. Claude Haiku 5.5 (&lt;code&gt;claude-haiku-5-5&lt;/code&gt;) is one current option. Anthropic positions Haiku 5.5 as its fastest and most cost-efficient model, and it suits summarization. &lt;code&gt;deepseek-v4-flash&lt;/code&gt; is a low-cost alternative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Last known state:&lt;/strong&gt; the final code snippet, error message, and relevant configuration the assistant produced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reasoning summary:&lt;/strong&gt; a short digest of the assistant’s reasoning where it went off track, plus a link to the full trace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Links:&lt;/strong&gt; the session replay, trace viewer, and CRM record.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat this payload as sensitive. It may contain customer code, secrets, or personal data. Redact credentials before posting, restrict the channel to the SE team, and align retention with your privacy policy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Designing Interactive Slack Claiming Cards
&lt;/h2&gt;

&lt;p&gt;Slack Block Kit lets you post a rich message with a button the SE can click to claim the escalation. Keep these documented limits in mind:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Element&lt;/th&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Header block text&lt;/td&gt;
&lt;td&gt;150 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Section block text&lt;/td&gt;
&lt;td&gt;3,000 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Section fields&lt;/td&gt;
&lt;td&gt;10 fields, 2,000 characters each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Button label&lt;/td&gt;
&lt;td&gt;75 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Button &lt;code&gt;value&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;2,000 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocks per message&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Truncate long code snippets and link to the full trace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Posting the Card with Node.js and Slack Bolt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@slack/bolt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;signingSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_SIGNING_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Keep the snippet inside Slack's 3,000-character section limit.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;truncate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;postEscalationCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;escalation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;prospectName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;problemSummary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;codeSnippet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;escalationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;escalation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Fallback text used in notifications and accessibility contexts&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`New SE escalation: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Enterprise escalation: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Prospect:*\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prospectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Tier:*\n&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Escalation ID:*\n&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;escalationId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Assistant model:*\n&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;modelName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`*Problem summary:*\n&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problemSummary&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Failing code context:*\n&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeSnippet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;actions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;block_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`escalation_actions_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;escalationId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Claim escalation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claim_escalation_button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;escalationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;View full trace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://internal.example.com/traces/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;escalationId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;view_trace_link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error posting Slack escalation card:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// let the caller retry or alert&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Link buttons still send an interaction payload to your app.&lt;/span&gt;
&lt;span class="c1"&gt;// Acknowledge it so Slack doesn't show an error.&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;view_trace_link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ack&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Slack’s mrkdwn code fences don’t support language-specific syntax highlighting, so the example omits a language tag.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Handling the “Claim” Action and State Locking
&lt;/h2&gt;

&lt;p&gt;When an SE clicks &lt;strong&gt;Claim escalation&lt;/strong&gt;, Slack sends a &lt;code&gt;block_actions&lt;/code&gt; payload to your interactivity endpoint. Your handler should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledge immediately.&lt;/strong&gt; Slack expects an acknowledgement within 3 seconds, so call &lt;code&gt;ack()&lt;/code&gt; first and do slow work afterwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claim atomically.&lt;/strong&gt; Two SEs can click at nearly the same moment, so check-then-write isn’t safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update the card&lt;/strong&gt; to show the owner and remove the button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start the handoff.&lt;/strong&gt; Create a thread or private channel and notify the prospect.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Atomic Locking
&lt;/h3&gt;

&lt;p&gt;Let the database arbitrate. In SQL, a conditional update does it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;escalations&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;claimed_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claimed_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;claimed_by&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- If 1 row was affected, this SE won the claim.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Redis, &lt;code&gt;SET escalation:{id}:owner {userId} NX&lt;/code&gt; achieves the same result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling the Click in Bolt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claim_escalation_button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Acknowledge within Slack's 3-second window.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escalationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Slack user IDs are stable; display names are not&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messageTs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Atomic claim (see SQL / Redis examples above)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claimed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;atomicLockEscalation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;escalationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;claimed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postEphemeral&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This escalation has already been claimed by another Solutions Engineer.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 2. Replace the actions block with a status line&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;actions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unixTs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// Slack renders &amp;lt;!date^...&amp;gt; in each viewer's own time zone&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Claimed by &amp;lt;@&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt; at &amp;lt;!date^&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;unixTs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;^{time}|just now&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messageTs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Escalation claimed by &amp;lt;@&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Start the handoff (thread, private channel, or Slack Connect)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;initializeSEHandoffSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;escalationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to update Slack card or start handoff:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reaching the Prospect
&lt;/h3&gt;

&lt;p&gt;Notify the prospect in the surface they’re already using, such as the in-app chat. For a live conversation with an external customer, Slack Connect channels are an option if both organizations use Slack. Otherwise, relay messages through your own backend or move the conversation to email or a call. Don’t promise a response time in the notification unless your team can meet it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Model Considerations When Reviewing an Escalation
&lt;/h2&gt;

&lt;p&gt;Teams often find it useful to tell their SEs which model handled the session. The notes below are general tendencies, not guarantees. Validate them against your own escalation data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Models
&lt;/h3&gt;

&lt;p&gt;Anthropic’s current lineup consists of Opus 5.5 (&lt;code&gt;claude-opus-5-5&lt;/code&gt;), Sonnet 5.5 (&lt;code&gt;claude-sonnet-5-5&lt;/code&gt;), and Haiku 5.5 (&lt;code&gt;claude-haiku-5-5&lt;/code&gt;). Anthropic positions Sonnet 5.5 as a faster, lower-cost model for well-scoped everyday coding and agent work, and Opus 5.5 for long-running agentic coding and complex, open-ended problems.&lt;/p&gt;

&lt;p&gt;When reviewing a Claude-handled escalation, check for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API methods or parameters that don’t exist in your SDK (hallucinated or outdated references).&lt;/li&gt;
&lt;li&gt;Missing configuration, incorrect headers, or environment-specific auth mismatches.&lt;/li&gt;
&lt;li&gt;Constraints the user stated early in a long session that were later dropped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DeepSeek Models
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;deepseek-v4-pro&lt;/code&gt; is DeepSeek’s flagship, and &lt;code&gt;deepseek-v4-flash&lt;/code&gt; is the faster, cheaper tier. Both expose thinking mode, so SEs can read the chain of thought.&lt;/p&gt;

&lt;p&gt;When reviewing a DeepSeek-handled escalation, check for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrong assumptions in the reasoning trace about the customer’s data model or infrastructure.&lt;/li&gt;
&lt;li&gt;Long reasoning that ends in a confident but unverified answer.&lt;/li&gt;
&lt;li&gt;Missing reasoning context in multi-turn tool-calling flows, a failure DeepSeek’s own docs and community integrations discuss.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Both
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Compare the model’s claims against your actual API reference before relaying them.&lt;/li&gt;
&lt;li&gt;Feed confirmed gaps back into your documentation, retrieval index, and system prompts.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Measuring Success: Metrics for AI-to-SE Routing
&lt;/h2&gt;

&lt;p&gt;Track these metrics before and after launch, ideally against a control group that still uses the email queue:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Escalation conversion rate.&lt;/strong&gt; What share of prospects who escalate go on to a paid plan or a successful production integration?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to claim.&lt;/strong&gt; The gap between the escalation firing and an SE clicking Claim. Set a target that fits your team’s coverage, for example a few minutes during business hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to resolution (TTR).&lt;/strong&gt; Compare Slack-claimed escalations with the email-support baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalation quality.&lt;/strong&gt; SE-rated usefulness of each handoff, and the percentage of escalations the assistant could have handled itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deflection vs. escalation ratio.&lt;/strong&gt; As you tune prompts, retrieval, and model choice, unnecessary escalations should fall while the quality of the remaining ones rises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unclaimed escalations.&lt;/strong&gt; Alert when a card sits unclaimed past a threshold, and re-ping or route it to a backup SE.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Operational Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model churn.&lt;/strong&gt; Model names and retirement dates change often. Both Claude 3.5 Sonnet and DeepSeek’s legacy &lt;code&gt;deepseek-chat&lt;/code&gt; and &lt;code&gt;deepseek-reasoner&lt;/code&gt; aliases are gone. Keep model IDs in configuration, not hard-coded, and subscribe to provider deprecation notices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing changes.&lt;/strong&gt; DeepSeek announced peak and off-peak pricing effective August 16, 2026. Re-estimate costs if you run summarization or triage at volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limits.&lt;/strong&gt; Slack’s Web API is rate limited per method. Batch non-urgent updates and handle &lt;code&gt;429&lt;/code&gt; responses with the &lt;code&gt;Retry-After&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security.&lt;/strong&gt; Verify Slack request signatures (Bolt does this when you provide the signing secret), scope your bot token to the minimum permissions, and restrict who can access escalation channels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure handling.&lt;/strong&gt; If Slack is unreachable, queue the escalation and fall back to email or another alert so a prospect is never silently dropped.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion and Next Steps
&lt;/h2&gt;

&lt;p&gt;AI coding assistants resolve many developer questions, but enterprise deals often hinge on the ones they can’t. Routing those escalations into Slack with full context and a claim card puts a human on the problem quickly. It also gives you data to improve the assistant.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define your triggers.&lt;/strong&gt; Combine explicit user requests, repeated-failure heuristics, and account-based rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build your payload schema.&lt;/strong&gt; Capture code, errors, reasoning, and account metadata, with credentials redacted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship the Block Kit card and atomic claim logic.&lt;/strong&gt; Acknowledge within 3 seconds and lock claims in your database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure and iterate.&lt;/strong&gt; Track time to claim, TTR, and conversion against a baseline, and use recurring escalation themes to improve documentation and prompts.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic, Model deprecations: &lt;a href="https://platform.claude.com/docs/en/about-claude/model-deprecations" rel="noopener noreferrer"&gt;https://platform.claude.com/docs/en/about-claude/model-deprecations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DeepSeek API change log: &lt;a href="https://api-docs.deepseek.com/updates/" rel="noopener noreferrer"&gt;https://api-docs.deepseek.com/updates/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DeepSeek V4 Preview release: &lt;a href="https://api-docs.deepseek.com/news/news260424/" rel="noopener noreferrer"&gt;https://api-docs.deepseek.com/news/news260424/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DeepSeek thinking mode guide: &lt;a href="https://api-docs.deepseek.com/guides/thinking_mode/" rel="noopener noreferrer"&gt;https://api-docs.deepseek.com/guides/thinking_mode/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Slack Block Kit reference: &lt;a href="https://api.slack.com/reference/messaging/blocks" rel="noopener noreferrer"&gt;https://api.slack.com/reference/messaging/blocks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bolt for JavaScript: &lt;a href="https://github.com/slackapi/bolt-js" rel="noopener noreferrer"&gt;https://github.com/slackapi/bolt-js&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/routing-deepseek-claude-coding-assistant-escalations-directly-to-solutions-engineers-in-slack" rel="noopener noreferrer"&gt;https://instachime.com/blog/routing-deepseek-claude-coding-assistant-escalations-directly-to-solutions-engineers-in-slack&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
    </item>
    <item>
      <title>Routing OpenAI’s Realtime API Voice Sessions Directly to Human Sales War Rooms: A High-Ticket HITL Architecture</title>
      <dc:creator>InstaChime</dc:creator>
      <pubDate>Thu, 15 Oct 2026 01:54:16 +0000</pubDate>
      <link>https://dev.to/instachime/routing-openais-realtime-api-voice-sessions-directly-to-human-sales-war-rooms-a-high-ticket-hitl-29e5</link>
      <guid>https://dev.to/instachime/routing-openais-realtime-api-voice-sessions-directly-to-human-sales-war-rooms-a-high-ticket-hitl-29e5</guid>
      <description>&lt;p&gt;&lt;em&gt;Verified against OpenAI’s developer documentation and changelog as of October 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In high-ticket B2B sales, financial services, and other consultative selling, a fully autonomous voice agent is risky. OpenAI’s &lt;strong&gt;Realtime API&lt;/strong&gt; gives you natural speech-to-speech conversation with tool calling. But a mispriced SLA, a fumbled security question, or a missed negotiation cue on a six-figure deal is expensive.&lt;/p&gt;

&lt;p&gt;A practical answer is &lt;strong&gt;human-in-the-loop (HITL)&lt;/strong&gt; design. The AI answers instantly, qualifies the buyer, and handles routine questions. Every live call is mirrored into a &lt;strong&gt;sales war room&lt;/strong&gt; (Slack, Microsoft Teams, or a custom console), where a human can watch, steer, or take over.&lt;/p&gt;

&lt;p&gt;This guide covers the architecture end to end: how the current Realtime API works, how to stream a call into a war room, and how to execute a takeover or a “whisper” intervention. It also covers the production issues that usually get missed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on API versions.&lt;/strong&gt; OpenAI removed the Realtime API &lt;strong&gt;Beta&lt;/strong&gt; interface on May 12, 2026. Older tutorials using &lt;code&gt;gpt-4o-realtime-preview&lt;/code&gt;, the &lt;code&gt;OpenAI-Beta: realtime=v1&lt;/code&gt; header, and event names like &lt;code&gt;response.audio_transcript.delta&lt;/code&gt; no longer apply. Everything below uses the GA interface.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Speed-to-Human Matters
&lt;/h2&gt;

&lt;p&gt;Two frequently cited studies explain why a fast human handoff is worth engineering for. Both measure response time to &lt;strong&gt;web leads&lt;/strong&gt;, not AI voice, so treat them as directional rather than as a forecast for your pipeline.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Harvard Business Review (2011), “The Short Life of Online Sales Leads.”&lt;/strong&gt; The authors analyzed about 1.25 million leads across 29 B2C and 13 B2B companies. Firms that contacted a lead within an hour were nearly &lt;strong&gt;7 times&lt;/strong&gt; as likely to qualify it as firms that waited even one more hour. They were more than &lt;strong&gt;60 times&lt;/strong&gt; as likely as firms that waited 24 hours or longer. “Qualify” meant having a meaningful conversation with a decision maker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lead Response Management study (2007, InsideSales.com and MIT).&lt;/strong&gt; Qualification odds were about &lt;strong&gt;21 times&lt;/strong&gt; higher when contact happened within 5 minutes rather than 30 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An AI voice agent can answer in seconds, and a war room shortens the second delay: the time from “this buyer is serious” to “a human closer is in the conversation.”&lt;/p&gt;

&lt;p&gt;AI-first handling and human closing split the work along their strengths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Coverage.&lt;/strong&gt; The AI answers every inbound call at any hour, including overflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qualification.&lt;/strong&gt; It collects budget, authority, need, and timeline (BANT) consistently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judgment.&lt;/strong&gt; Humans take over for contract negotiation, edge-case technical questions, and relationship building.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Choosing Your Voice Stack (October 2026)
&lt;/h2&gt;

&lt;p&gt;OpenAI’s voice lineup has changed quickly this year:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gpt-realtime-2.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Released July 6, 2026&lt;/td&gt;
&lt;td&gt;OpenAI’s docs list it as the model for low-latency voice agents. Improvements include alphanumeric recognition, silence and noise handling, and interruption behavior.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gpt-realtime-2.1-mini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Released July 6, 2026&lt;/td&gt;
&lt;td&gt;A faster, lower-cost distilled reasoning model for high-volume lines.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gpt-realtime-2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Released May 7, 2026&lt;/td&gt;
&lt;td&gt;The first Realtime model with configurable reasoning effort.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;GPT-Live&lt;/strong&gt; (&lt;code&gt;gpt-live-1&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;GA September 10, 2026&lt;/td&gt;
&lt;td&gt;A different, full-duplex API (&lt;code&gt;v1/live/sessions&lt;/code&gt;) that keeps talking while a backend model or agent handles reasoning and tools. Not a drop-in replacement; see the note below.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note on GPT-Live.&lt;/strong&gt; OpenAI’s migration guide says its session setup differs from Realtime, and that GPT-Live decides when to speak. Manual audio commits and &lt;code&gt;response.create&lt;/code&gt; turn triggers go away. The sideband pattern below carries over conceptually, but the whisper-injection code would need adapting. This article targets the Realtime API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transcription models are changing too.&lt;/strong&gt; OpenAI announced on August 26, 2026 that &lt;code&gt;whisper-1&lt;/code&gt;, &lt;code&gt;gpt-4o-transcribe&lt;/code&gt;, &lt;code&gt;gpt-4o-mini-transcribe&lt;/code&gt;, and &lt;code&gt;gpt-4o-transcribe-diarize&lt;/code&gt; will shut down on &lt;strong&gt;February 26, 2027&lt;/strong&gt;. Use &lt;code&gt;gpt-transcribe&lt;/code&gt; (final transcripts of committed Realtime turns) or &lt;code&gt;gpt-live-transcribe&lt;/code&gt; (low-latency streaming) in new builds.&lt;/p&gt;




&lt;h2&gt;
  
  
  System Architecture
&lt;/h2&gt;

&lt;p&gt;The system bridges two domains:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The voice domain.&lt;/strong&gt; Audio between the caller, your telephony provider, and OpenAI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The war room domain.&lt;/strong&gt; Structured events (transcript turns, intent scores, alerts) pushed to humans, and human commands pushed back.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Recommended topology: SIP plus a sideband connection
&lt;/h3&gt;

&lt;p&gt;OpenAI supports direct SIP connections. Your SIP trunking provider (Twilio is OpenAI’s example) sends calls to &lt;code&gt;sip:$PROJECT_ID@sip.api.openai.com;transport=tls&lt;/code&gt;. OpenAI fires a &lt;code&gt;realtime.call.incoming&lt;/code&gt; webhook to your server. You accept the call with a session configuration, then open a &lt;strong&gt;sideband&lt;/strong&gt; WebSocket to the same call.&lt;/p&gt;

&lt;p&gt;OpenAI’s docs describe a sideband as two active connections to the same Realtime session. One is the caller’s media path, and one is your server, which can monitor the session, update instructions, and respond to tool calls. For SIP, the WebSocket lives for the life of the call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Caller ──PSTN──▶ SIP trunk (e.g. Twilio) ──SIP/RTP──▶ OpenAI Realtime session
                                                         │
                        webhook: realtime.call.incoming  │  sideband WebSocket
                                                         ▼  wss://api.openai.com/v1/realtime?call_id=…
                                               ┌──────────────────────┐
                                               │  Your orchestrator   │
                                               │  (Node.js / Python)  │
                                               └─────────┬────────────┘
                                                         │ events
                                              Redis / NATS / Kafka bus
                                                         ▼
                                  War room: Slack · Microsoft Teams · custom console
                                                         │  rep clicks "Take over"
                                                         ▼
                      POST /v1/realtime/calls/{call_id}/refer  ──▶  rep's phone or SIP URI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your orchestrator never touches audio in this topology. It sees transcripts and events, which keeps it simple and keeps media latency off your servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternative topology: WebSocket plus Media Streams
&lt;/h3&gt;

&lt;p&gt;If you need to own the audio path, for example for recording or custom mixing, connect your telephony provider’s media stream to the Realtime API over WebSocket. With Twilio, a bidirectional &lt;code&gt;&amp;lt;Connect&amp;gt;&amp;lt;Stream&amp;gt;&lt;/code&gt; carries 8 kHz μ-law audio, which maps to the Realtime &lt;code&gt;audio/pcmu&lt;/code&gt; format. You get full control over takeover, because you can cut the OpenAI leg and bridge the caller elsewhere. The cost is that you now run an audio proxy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Telephony layer.&lt;/strong&gt; SIP trunk, Twilio Media Streams, or WebRTC (LiveKit, Daily).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestrator.&lt;/strong&gt; Receives webhooks, holds per-call state, executes tool calls, publishes war-room events, and applies human commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realtime session.&lt;/strong&gt; Speech-to-speech model with function calling. Sessions have a &lt;strong&gt;maximum duration of 60 minutes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event bus.&lt;/strong&gt; Redis Pub/Sub, NATS, or Kafka, so several war-room consumers can subscribe without coupling to the call handler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;War room front end.&lt;/strong&gt; Slack (Block Kit and Socket Mode), Microsoft Teams (bot and Adaptive Cards), or a custom web console.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Accept the Call and Configure the Session
&lt;/h2&gt;

&lt;p&gt;Verify OpenAI’s webhook signature (the official SDKs provide &lt;code&gt;webhooks.unwrap&lt;/code&gt;) and deduplicate by the &lt;code&gt;webhook-id&lt;/code&gt; header. Then accept the call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.openai.com/v1/realtime/calls/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/accept`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;realtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-realtime-2.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`You are a senior SDR for Acme Cloud. Qualify budget, authority, need, and timeline (BANT).
Never quote prices, SLAs, or contract terms. If the buyer mentions a budget over $50k, a deadline
inside 90 days, a competitor, security or compliance requirements, or asks for a person,
call escalate_to_war_room.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;output_modalities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/pcmu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;turn_detection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;semantic_vad&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;transcription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-transcribe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/pcmu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;marin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;escalate_to_war_room&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alert human sales staff that a live call needs a human. Use when high-ticket intent or any escalation criterion is met.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;intent_score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 to 10 buyer-fit score&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Why this call needs a human&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;buying_signals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;intent_score&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reason&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;tool_choice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details that differ from older tutorials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The session uses &lt;code&gt;type: "realtime"&lt;/code&gt; and nested &lt;code&gt;audio.input&lt;/code&gt; and &lt;code&gt;audio.output&lt;/code&gt; objects, not top-level &lt;code&gt;input_audio_format&lt;/code&gt; or &lt;code&gt;modalities&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;marin&lt;/code&gt; and &lt;code&gt;cedar&lt;/code&gt; are the voices OpenAI recommends for best quality. &lt;code&gt;verse&lt;/code&gt; still exists but is not the recommendation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;voice&lt;/code&gt; cannot be changed after the model has produced audio in a session.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;semantic_vad&lt;/code&gt; is one option for turn detection. Classic &lt;code&gt;server_vad&lt;/code&gt; is still available if you want tighter manual control. You can also keep VAD on but set &lt;code&gt;create_response&lt;/code&gt; and &lt;code&gt;interrupt_response&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; so your code decides when the model speaks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then open the sideband connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`wss://api.openai.com/v1/realtime?call_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Stream Transcripts and Detect Escalation
&lt;/h2&gt;

&lt;p&gt;These GA server events matter for a war room:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;conversation.item.input_audio_transcription.completed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The caller’s transcribed turn.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;response.output_audio_transcript.delta&lt;/code&gt; / &lt;code&gt;.done&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The AI’s spoken words (the GA names; the old &lt;code&gt;response.audio_transcript.*&lt;/code&gt; names are gone).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;response.created&lt;/code&gt; / &lt;code&gt;response.done&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Response lifecycle. &lt;code&gt;response.done&lt;/code&gt; carries completed function calls in &lt;code&gt;response.output&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_audio_buffer.speech_started&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The caller started speaking; the server cancels any in-progress response.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Escalation can come from three sources:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Model-initiated.&lt;/strong&gt; The model calls &lt;code&gt;escalate_to_war_room&lt;/code&gt; based on your instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heuristic.&lt;/strong&gt; A separate classifier scores transcripts for competitor mentions, objections, frustration, or deal-size signals. You can run it as an out-of-band response (&lt;code&gt;conversation: "none"&lt;/code&gt;, text-only), which OpenAI documents for classification tasks and which doesn’t pollute the live conversation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caller-initiated.&lt;/strong&gt; The caller asks for a person.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example: intercepting the tool call and alerting Slack
&lt;/h3&gt;

&lt;p&gt;The model’s function call arrives in &lt;code&gt;response.done&lt;/code&gt;. You must return a &lt;code&gt;function_call_output&lt;/code&gt; and then ask for a new response, so the caller hears something sensible instead of silence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@slack/web-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WAR_ROOM_CHANNEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_WAR_ROOM_CHANNEL_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;attachListeners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;responseActive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pendingDirectives&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;conversation.item.input_audio_transcription.completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`*Prospect:* &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response.output_audio_transcript.done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`*AI:* &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response.done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function_call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;escalate_to_war_room&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;postWarRoomAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Give the model a result, then let it speak to the caller.&lt;/span&gt;
            &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;conversation.item.create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function_call_output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;specialist_notified&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}));&lt;/span&gt;
            &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response.create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Tell the caller you're bringing in a senior colleague who can help, and keep them company for a moment.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}));&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;flushWhispers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// see the whisper section&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;postWarRoomAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WAR_ROOM_CHANNEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`High-ticket call needs a human: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Live call escalation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Prospect:* &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callerName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Intent score:* &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intent_score&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/10`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Trigger:* &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mrkdwn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`*Latest conversation:*\n&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2900&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;actions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;takeover_voice_call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Take over call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;callId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inject_whisper&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Whisper to AI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;callId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open_console&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plain_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Open live console&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://app.example.com/war-room/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes on the Slack side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slack limits &lt;code&gt;section&lt;/code&gt; text to 3,000 characters, so truncate transcripts.&lt;/li&gt;
&lt;li&gt;Interactive payloads (button clicks) and slash commands must be acknowledged within about three seconds. Acknowledge immediately and do the real work asynchronously.&lt;/li&gt;
&lt;li&gt;Slack’s &lt;strong&gt;Socket Mode&lt;/strong&gt; delivers interactivity over a WebSocket instead of a public HTTP endpoint.&lt;/li&gt;
&lt;li&gt;Microsoft Teams bots receive activities at an HTTPS messaging endpoint rather than over a socket. The architecture is the same, but the transport differs. Use Adaptive Cards for the alert.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Human Takeover
&lt;/h2&gt;

&lt;p&gt;Takeover mechanics depend on your topology.&lt;/p&gt;

&lt;h3&gt;
  
  
  SIP topology: transfer with REFER
&lt;/h3&gt;

&lt;p&gt;OpenAI exposes &lt;code&gt;POST /v1/realtime/calls/{call_id}/refer&lt;/code&gt;. You supply a &lt;code&gt;target_uri&lt;/code&gt; for the SIP &lt;code&gt;Refer-To&lt;/code&gt; header, such as &lt;code&gt;tel:+14155550123&lt;/code&gt; or &lt;code&gt;sip:agent@example.com&lt;/code&gt;. OpenAI returns &lt;code&gt;200 OK&lt;/code&gt; once the REFER is relayed to your SIP provider, and the downstream system handles the rest of the call flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;takeOver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repTargetUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Claim the call (see the locking section)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;acquireTakeoverLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repId&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;already claimed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Stop the AI mid-sentence (errors harmlessly if nothing is active)&lt;/span&gt;
  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response.cancel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Hand the call to the human&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.openai.com/v1/realtime/calls/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/refer`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;target_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repTargetUri&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Caveats for REFER-based takeover:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AI leaves the call at the transfer. The rep joins fresh, so the context handoff must happen in the war room: transcript, extracted fields, and the reason for escalation, delivered &lt;em&gt;before or as&lt;/em&gt; the phone rings.&lt;/li&gt;
&lt;li&gt;REFER support and behavior depend on your SIP provider. Test your carrier path, including failure and no-answer cases. If the rep doesn’t pick up, plan a fallback, such as the AI re-engaging or capturing a callback.&lt;/li&gt;
&lt;li&gt;For a smoother warm handoff, let the AI say a short transition line first, wait for &lt;code&gt;response.done&lt;/code&gt;, then refer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  WebSocket and Media Streams topology: bridge the audio yourself
&lt;/h3&gt;

&lt;p&gt;When you own the audio path, takeover means stopping the OpenAI leg and bridging the caller to the rep. With Twilio, you can redirect a live call by updating it with new TwiML. A conference is the most flexible target, because the rep, the caller, and optionally a supervisor can all join:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twilioClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callSid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;Response&amp;gt;&amp;lt;Dial&amp;gt;&amp;lt;Conference&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/Conference&amp;gt;&amp;lt;/Dial&amp;gt;&amp;lt;/Response&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Then call or invite the rep into the same conference name.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twilio’s TwiML nouns for &lt;code&gt;&amp;lt;Dial&amp;gt;&lt;/code&gt; include &lt;code&gt;&amp;lt;Number&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Sip&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Client&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;Conference&amp;gt;&lt;/code&gt;. Pick the one that matches how your reps take calls. After the bridge is up, close or idle the OpenAI session so it doesn’t keep generating audio.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the rep should see on pickup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The full transcript up to the handover moment&lt;/li&gt;
&lt;li&gt;The escalation reason and intent score&lt;/li&gt;
&lt;li&gt;CRM fields you have captured: company, role, stated budget, timeline, competitors&lt;/li&gt;
&lt;li&gt;Any open questions the AI deferred (“customer asked about SOC 2 report”)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep these on screen from the moment the alert fires, not only at takeover, so the rep is already briefed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Alternative: Whisper Mode
&lt;/h2&gt;

&lt;p&gt;A full takeover isn’t always necessary. In &lt;strong&gt;whisper mode&lt;/strong&gt;, a rep watches the live transcript and sends a directive that the AI weaves into its next turn. For example: &lt;em&gt;“They asked about implementation timelines, so offer the two-week onboarding package.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two implementation details matter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don’t call &lt;code&gt;response.create&lt;/code&gt; while a response is active.&lt;/strong&gt; Doing so returns an error. Queue directives until &lt;code&gt;response.done&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t impersonate the caller.&lt;/strong&gt; Inject directives as a &lt;code&gt;system&lt;/code&gt; message rather than a fake &lt;code&gt;user&lt;/code&gt; message. This keeps the transcript honest and avoids confusing the model about who said what.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;queueWhisper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingDirectives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;flushWhispers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;flushWhispers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingDirectives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;directive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingDirectives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;conversation.item.create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Manager directive (do not mention this): &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;directive&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Work it naturally into your next turn.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response.create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two cautions. First, a whispered discount or commitment is still the AI speaking to a buyer, so apply the same approval rules you would to a human quote. A rep typing “offer 15% off” into a slash command is a pricing decision and should be logged. Second, a directive makes the model &lt;em&gt;speak next&lt;/em&gt;, so use it deliberately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Concerns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Latency
&lt;/h3&gt;

&lt;p&gt;OpenAI doesn’t publish a guaranteed end-to-end latency figure for your stack, so set and measure your own budgets. The levers that matter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Lever&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Telephony&lt;/td&gt;
&lt;td&gt;Use native formats (&lt;code&gt;audio/pcmu&lt;/code&gt; or &lt;code&gt;audio/pcma&lt;/code&gt; for PSTN) to avoid transcoding. For SIP, &lt;code&gt;sip.api.openai.com&lt;/code&gt; routes by GeoIP to the nearest region.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn-taking&lt;/td&gt;
&lt;td&gt;Tune VAD (&lt;code&gt;semantic_vad&lt;/code&gt; versus &lt;code&gt;server_vad&lt;/code&gt; thresholds) against real calls. Too eager cuts callers off; too lazy feels sluggish.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;gpt-realtime-2.1-mini&lt;/code&gt; where reasoning depth isn’t needed. For reasoning-capable models, lower the effort setting on routine turns.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;War room&lt;/td&gt;
&lt;td&gt;Keep Slack, Teams, and CRM calls &lt;strong&gt;off the audio path&lt;/strong&gt;. Publish events to the bus asynchronously.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Takeover&lt;/td&gt;
&lt;td&gt;Pre-register reps (phone or SIP endpoint) and keep an on-call roster in memory so a click doesn’t wait on a lookup.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Race conditions: lock the call
&lt;/h3&gt;

&lt;p&gt;When a rep clicks “Take over,” several things can happen at once: the AI is mid-sentence, a second rep clicks, and the caller is talking. Use a distributed lock keyed by call, with an owner token and renewal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LOCK_TTL_SEC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;acquireTakeoverLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`lock:call:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LOCK_TTL_SEC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Keep the lock alive for as long as the rep owns the call (a call outlasts 30 s)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renewTakeoverLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
    if redis.call("get", KEYS[1]) == ARGV[1] then
      return redis.call("expire", KEYS[1], ARGV[2])
    end
    return 0`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`lock:call:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LOCK_TTL_SEC&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;releaseTakeoverLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
    if redis.call("get", KEYS[1]) == ARGV[1] then
      return redis.call("del", KEYS[1])
    end
    return 0`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`lock:call:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A short TTL prevents deadlocks if your process dies, but a takeover lasts minutes, so renew it on a heartbeat and release it only if you still own it. While a rep holds the lock, the orchestrator should ignore further AI commands for that call, including whispers and &lt;code&gt;response.create&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security and compliance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verify webhooks.&lt;/strong&gt; Check OpenAI’s signature, reject stale timestamps, and deduplicate by &lt;code&gt;webhook-id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protect the SIP edge.&lt;/strong&gt; OpenAI publishes dedicated SIP IP ranges (added January 2026) for allowlisting. The API platform also offers IP allowlists and mutual TLS (generally available since August 2026).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never put the API key in a browser.&lt;/strong&gt; Keep tool logic and secrets on your server. That is the reason OpenAI recommends the sideband pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redact before you broadcast.&lt;/strong&gt; War-room channels have broad audiences. Mask card numbers, SSNs, and similar values before posting transcripts to Slack or Teams, and apply retention rules to the war-room logs themselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RBAC on takeover.&lt;/strong&gt; Restrict “Take over” and “Whisper” to authorized reps for that deal tier, and log who did what.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data retention.&lt;/strong&gt; OpenAI offers approved customers Zero Data Retention or Modified Abuse Monitoring, but eligibility is endpoint-specific. Check OpenAI’s “Your data” page for the current Realtime status rather than assuming. The API platform also added an in-product flow in October 2026 for eligible organizations to accept a HIPAA Business Associate Agreement. Confirm which services it covers before routing regulated audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consent and disclosure.&lt;/strong&gt; Recording and call-monitoring consent laws vary by jurisdiction. In the US, the FCC ruled in February 2024 that AI-generated voices count as “artificial” under the TCPA, which affects outbound calling. Involve counsel before launching outbound campaigns or recording calls.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Measuring Impact
&lt;/h2&gt;

&lt;p&gt;Avoid quoting vendor-style conversion lifts you haven’t measured. Instead, baseline your current process and track:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time to first response.&lt;/strong&gt; Lead created to first live conversation. Compare to the HBR and InsideSales benchmarks above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalation-to-human time.&lt;/strong&gt; Alert fired to rep speaking with the caller.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalation precision.&lt;/strong&gt; Share of alerts that were worth a human’s time. Low precision trains reps to ignore the channel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handoff loss.&lt;/strong&gt; Callers who hang up between the AI’s transition line and the rep joining.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalated versus non-escalated outcomes.&lt;/strong&gt; Meetings booked, qualified opportunities, and pipeline value, segmented by deal size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI containment on routine calls.&lt;/strong&gt; Calls fully resolved or booked without human involvement, and the error rate on those.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run the system in &lt;strong&gt;shadow mode&lt;/strong&gt; first: AI answering, humans only watching. That gives you real transcripts to tune escalation criteria before anyone can take over.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The core idea is to treat the AI voice agent as the first responder and the human as the closer, with the war room as the control plane between them. OpenAI’s GA Realtime API gives you the pieces: SIP ingestion, webhook-driven call acceptance, a sideband WebSocket for server-side control, and REFER for transfers. The orchestration is yours to build, along with the discipline around locking, redaction, consent, and escalation quality that determines whether reps trust the system.&lt;/p&gt;

&lt;p&gt;If you’re starting today, begin with the SIP-plus-sideband topology, &lt;code&gt;gpt-realtime-2.1&lt;/code&gt; or its mini variant, shadow mode, and a small set of well-tested escalation triggers. Expand from there.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI, &lt;em&gt;Realtime API with SIP&lt;/em&gt;: &lt;a href="https://developers.openai.com/api/docs/guides/realtime-sip" rel="noopener noreferrer"&gt;https://developers.openai.com/api/docs/guides/realtime-sip&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;em&gt;Webhooks and server-side controls&lt;/em&gt;: &lt;a href="https://developers.openai.com/api/docs/guides/realtime-server-controls" rel="noopener noreferrer"&gt;https://developers.openai.com/api/docs/guides/realtime-server-controls&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;em&gt;Realtime conversations&lt;/em&gt;: &lt;a href="https://developers.openai.com/api/docs/guides/realtime-conversations" rel="noopener noreferrer"&gt;https://developers.openai.com/api/docs/guides/realtime-conversations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;em&gt;API changelog&lt;/em&gt;: &lt;a href="https://developers.openai.com/api/docs/changelog" rel="noopener noreferrer"&gt;https://developers.openai.com/api/docs/changelog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;em&gt;Migrate to GPT-Live&lt;/em&gt;: &lt;a href="https://developers.openai.com/api/docs/guides/live-migration" rel="noopener noreferrer"&gt;https://developers.openai.com/api/docs/guides/live-migration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Oldroyd, McElheran, and Elkington, “The Short Life of Online Sales Leads,” &lt;em&gt;Harvard Business Review&lt;/em&gt;, 2011&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instachime.com/blog/routing-openai-s-realtime-api-voice-sessions-directly-to-human-sales-war-rooms-a-high-ticket-hitl-architecture" rel="noopener noreferrer"&gt;https://instachime.com/blog/routing-openai-s-realtime-api-voice-sessions-directly-to-human-sales-war-rooms-a-high-ticket-hitl-architecture&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
    </item>
  </channel>
</rss>
