{"id":3010,"date":"2026-06-22T06:54:51","date_gmt":"2026-06-22T06:54:51","guid":{"rendered":"https:\/\/oka.how\/?p=3010"},"modified":"2026-06-22T07:06:34","modified_gmt":"2026-06-22T07:06:34","slug":"from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges","status":"publish","type":"post","link":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/","title":{"rendered":"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges"},"content":{"rendered":"\r\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\r\n\r\n\r\n\r\n<h1 class=\"wp-block-heading\">From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges<\/h1>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">An alert that fires but reaches no one is indistinguishable from no alert at all. A smoke detector that only blinks a red light in the server room is useless if the operations team is on a different floor, working in a different tool, at a different hour. Detection is not the end of the story, delivery is.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><a href=\"\/\" target=\"_blank\" rel=\"noreferrer noopener\">OKA<\/a>, developed by UCit, is an all-in-one HPC analytics platform that ships with over 20 native dashboards covering resource utilization, user behavior, job efficiency, sustainability reporting, and cost tracking: all without requiring custom development from your team. It ingests data from your job scheduler (Slurm, PBS, LSF, \u2026) and immediately surfaces actionable insights through its built-in modules: statistical analysis, anomaly detection, forecasting, and automated reporting.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">In this series, we have been building up the layers of observability that turn raw scheduler data into operational intelligence. <a id=\"2824\" href=\"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/\" target=\"_blank\" rel=\"noreferrer noopener\" type=\"post\">In a first article<\/a>, we showed how <strong>Data Enhancers<\/strong> enrich that raw data with custom columns: energy readings from EAR, carbon footprint from the electricity grid, and cost figures from your internal pricing model\u2026 <a id=\"2977\" href=\"https:\/\/oka.how\/index.php\/2026\/06\/19\/monitoring-your-hpc-cluster-with-oka-healthcheck-rules\/\" target=\"_blank\" rel=\"noreferrer noopener\" type=\"post\">In a second article<\/a>, we covered <strong>Healthcheck Rules<\/strong>: Python scripts that run on a schedule inside OKA, monitor enriched metrics for anomalies, and fire structured alerts when a threshold is exceeded.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">This article covers the third and final layer: <strong>Webhook Bridges<\/strong>. When a Healthcheck fires (a GPU partition sitting idle above its threshold, a user&#8217;s job running past its expected wall time, a burst of failed jobs&#8230;) OKA can call any HTTP endpoint you configure in its notification profile. A webhook bridge is a small, self-contained Python microservice that sits at that endpoint, translates the OKA notification into the native format of a target tool, and forwards it. Slack, email, Jira, or a full PDF report: the bridge decides what happens next.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">This article walks through four open-source examples from the <a href=\"https:\/\/bitbucket.org\/ucit\/oka_webhooks\" target=\"_blank\" rel=\"noreferrer noopener\"><code>oka_webhooks<\/code> repository<\/a><strong><mark class=\"has-inline-color has-vivid-cyan-blue-color\" style=\"background-color: rgba(0, 0, 0, 0);\"> (coming soon!)<\/mark><\/strong>. Each addresses a concrete integration need. Whether you adopt one as-is or use it as a starting point, these examples cover some of the most common destinations we encounter at HPC operations teams.<\/p>\r\n\r\n\r\n\r\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\r\n<p class=\"wp-block-paragraph\"><mark class=\"has-inline-color has-vivid-cyan-blue-color\" style=\"background-color: rgba(0, 0, 0, 0);\"><strong>COMING SOON!<\/strong><\/mark> The source code for all bridges discussed here is available in the <a href=\"https:\/\/bitbucket.org\/ucit\/oka_webhooks\" target=\"_blank\" rel=\"noreferrer noopener\"><code>oka_webhooks<\/code> repository<\/a>.<\/p>\r\n<\/blockquote>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"342\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-1024x342.png\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-1024x342.png\" alt=\"\" class=\"lazyload wp-image-3029\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271024%27%20height%3D%27342%27%20viewBox%3D%270%200%201024%20342%27%3E%3Crect%20width%3D%271024%27%20height%3D%27342%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-200x67.png 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-300x100.png 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-400x134.png 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-600x200.png 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-768x257.png 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-800x267.png 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-1024x342.png 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-1200x401.png 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-1536x513.png 1536w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list.png 1718w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\r\n\r\n\r\n\r\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">How OKA triggers a webhook<\/h2>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">Before looking at the individual bridges, it helps to understand the mechanism that activates them.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">OKA&#8217;s <strong>Notification Profiles<\/strong> (configured in the admin panel under <strong>Management \u2192 Notifications<\/strong>) connect a set of healthchecks to one or more delivery channels. A single profile can combine several channel types simultaneously: <strong>email<\/strong> (delivered directly by OKA via SMTP), <strong>file<\/strong> (appended to a log file on the server), and <strong>webhook<\/strong> (an HTTP POST to any URL you configure). When a healthcheck breaches its threshold, OKA evaluates the profile and fires every channel in it, so you can send an internal email and trigger a Slack bridge at the same time with a single profile. For webhook channels, the payload is always the same minimal JSON structure:<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6\">JSON<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>{\r\n  \"subject\": \"&#91;OKA Healthcheck&#93; High GPU idle rate on partition gpu: High\",\r\n  \"body\": \"Partition: gpu\\nIdle GPU nodes: 12\/32 (37.5%)\\nThreshold: 20%\\n...\"\r\n}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F6F6F4\">{<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #97E2F2\">&quot;<\/span><span style=\"color: #97E1F1\">subject<\/span><span style=\"color: #97E2F2\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">&#91;OKA Healthcheck&#93; High GPU idle rate on partition gpu: High<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">  <\/span><span style=\"color: #97E2F2\">&quot;<\/span><span style=\"color: #97E1F1\">body<\/span><span style=\"color: #97E2F2\">&quot;<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Partition: gpu<\/span><span style=\"color: #F286C4\">\\n<\/span><span style=\"color: #E7EE98\">Idle GPU nodes: 12\/32 (37.5%)<\/span><span style=\"color: #F286C4\">\\n<\/span><span style=\"color: #E7EE98\">Threshold: 20%<\/span><span style=\"color: #F286C4\">\\n<\/span><span style=\"color: #E7EE98\">...<\/span><span style=\"color: #DEE492\">&quot;<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span><\/code><\/pre><\/div>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><br>The subject follows the pattern <code>[OKA Healthcheck] &lt;name&gt;: &lt;Level&gt;<\/code>, where <code>&lt;Level&gt;<\/code> is one of <code>Critical<\/code>, <code>High<\/code>, <code>Medium<\/code>, <code>Low<\/code>, or <code>Ok<\/code>. The body contains the full alert detail (which nodes, which metrics, which thresholds) formatted as plain text. This is all the bridges need to work with.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">Every bridge in this repository exposes two endpoints: <code>POST \/notify<\/code> for the standard JSON payload, and <code>GET \/notify<\/code> accepting the same fields as query parameters: useful for testing from a browser or curl without constructing a JSON body. All bridges also expose a <code>GET \/health<\/code> liveness endpoint used by systemd and monitoring tools to confirm the service is running.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">The security model is simple: an optional shared secret (<code>BRIDGE_BEARER_TOKEN<\/code>) is compared against the <code>Authorization: Bearer &lt;token&gt;<\/code> header that OKA sends with each notification. If the variable is not set, the endpoint accepts all incoming requests: appropriate for internal networks; recommended for deployments reachable from the internet.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">In production, each bridge runs as a <strong>systemd service<\/strong> backed by two gunicorn workers, with automatic restart on failure. A <code>.env<\/code> file holds the configuration; the included <code>.env.example<\/code> documents every variable. No database, no persistent state (with one exception, discussed below), nothing beyond Flask and the target system&#8217;s SDK or API client.<\/p>\r\n\r\n\r\n<div class=\"wp-block-image\">\r\n<figure class=\"aligncenter size-full\"><img decoding=\"async\" width=\"1701\" height=\"1211\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list.png\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list.png\" alt=\"\" class=\"lazyload wp-image-2952\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271701%27%20height%3D%271211%27%20viewBox%3D%270%200%201701%201211%27%3E%3Crect%20width%3D%271701%27%20height%3D%271211%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list-200x142.png 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list-300x214.png 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list-400x285.png 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list-600x427.png 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list-768x547.png 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list-800x570.png 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list-1024x729.png 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list-1200x854.png 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list-1536x1094.png 1536w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA_healthcheck-rules-list.png 1701w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 1701px) 100vw, 1701px\" \/><\/figure>\r\n<\/div>\r\n\r\n\r\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">The bridges: a use-case tour<\/h2>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">Instant visibility: the Slack bridge<\/h3>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">The simplest bridge in the collection has exactly one required configuration variable: <code>SLACK_WEBHOOK_URL<\/code>. That is the URL from Slack&#8217;s Incoming Webhooks feature: no bot token, no OAuth scope management, no Slack app installation beyond creating the webhook in the Slack UI.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">When OKA fires, the bridge extracts the alert level from the subject line using a regex and maps it to a status emoji:<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6\">Python<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>STATUS_EMOJI: dict&#91;str, str&#93; = {\r\n    \"critical\": \":red_circle:\",\r\n    \"high\":     \":red_circle:\",\r\n    \"error\":    \":red_circle:\",\r\n    \"warning\":  \":large_yellow_circle:\",\r\n    \"medium\":   \":large_yellow_circle:\",\r\n    \"ok\":       \":large_green_circle:\",\r\n    \"resolved\": \":large_green_circle:\",\r\n    \"info\":     \":large_blue_circle:\",\r\n}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #BF9EEE\">STATUS_EMOJI<\/span><span style=\"color: #F6F6F4\">: dict&#91;<\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> {<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">critical<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">:red_circle:<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">high<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">:     <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">:red_circle:<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">error<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">:    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">:red_circle:<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">warning<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">:  <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">:large_yellow_circle:<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">medium<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">:   <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">:large_yellow_circle:<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">ok<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">:       <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">:large_green_circle:<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">resolved<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">:large_green_circle:<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">info<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">:     <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">:large_blue_circle:<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">}<\/span><\/span><\/code><\/pre><\/div>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><br>The resulting Slack message is built as a <strong>Block Kit<\/strong> payload: a section header with the emoji prefix and the bold subject, followed by the alert body in a code block (preserving the line-by-line formatting), a UTC timestamp in the footer, and a divider.<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6\">Python<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>blocks = [\r\n    {\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": f\"{emoji} *{subject}*\"}},\r\n    {\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": f\"```{body}```\"}},\r\n    {\"type\": \"context\",  \"elements\": &#91;{\"type\": \"mrkdwn\", \"text\": ts}&#93;},\r\n    {\"type\": \"divider\"},\r\n]<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F6F6F4\">blocks <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> [<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">type<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">section<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">text<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">type<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">mrkdwn<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">text<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #F286C4\">f<\/span><span style=\"color: #E7EE98\">&quot;<\/span><span style=\"color: #BF9EEE\">{<\/span><span style=\"color: #F6F6F4\">emoji<\/span><span style=\"color: #BF9EEE\">}<\/span><span style=\"color: #E7EE98\"> *<\/span><span style=\"color: #BF9EEE\">{<\/span><span style=\"color: #F6F6F4\">subject<\/span><span style=\"color: #BF9EEE\">}<\/span><span style=\"color: #E7EE98\">*&quot;<\/span><span style=\"color: #F6F6F4\">}},<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">type<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">section<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">text<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">type<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">mrkdwn<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">text<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #F286C4\">f<\/span><span style=\"color: #E7EE98\">&quot;```<\/span><span style=\"color: #BF9EEE\">{<\/span><span style=\"color: #F6F6F4\">body<\/span><span style=\"color: #BF9EEE\">}<\/span><span style=\"color: #E7EE98\">```&quot;<\/span><span style=\"color: #F6F6F4\">}},<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">type<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">context<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">,  <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">elements<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: &#91;{<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">type<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">mrkdwn<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">text<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: ts}&#93;},<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">type<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">divider<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">},<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">]<\/span><\/span><\/code><\/pre><\/div>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><br>The message lands in whichever channel the Incoming Webhook is connected to. No stateful component, no external API call beyond the Slack webhook itself: the bridge processes a notification and returns in milliseconds.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><strong>Why this matters.<\/strong> Slack has become the de facto war-room for HPC operations teams. Routing OKA alerts into the right channel (whether that is <code>#hpc-oncall<\/code>, <code>#sustainability-kpis<\/code>, or <code>#cluster-finance<\/code>) means engineers see anomalies the moment they fire, without polling a dashboard. The emoji-coded severity gives an instant triage signal in a busy channel feed, and the code-block body preserves every line of alert detail without truncation.<\/p>\r\n\r\n\r\n<div class=\"wp-block-image\">\r\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" width=\"1024\" height=\"730\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification-1024x730.png\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification-1024x730.png\" alt=\"\" class=\"lazyload wp-image-3027\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271024%27%20height%3D%27730%27%20viewBox%3D%270%200%201024%20730%27%3E%3Crect%20width%3D%271024%27%20height%3D%27730%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification-200x143.png 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification-300x214.png 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification-400x285.png 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification-600x428.png 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification-768x548.png 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification-800x571.png 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification-1024x730.png 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/slack-notification.png 1092w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\r\n<\/div>\r\n\r\n\r\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">Reaching every stakeholder: the AWS SES bridge<\/h3>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">OKA natively supports SMTP-based email notifications out of the box: so if your infrastructure includes a standard mail relay or SMTP server, you do not need this bridge at all. This bridge is for teams that rely on cloud-native transactional email services instead: <strong>AWS Simple Email Service<\/strong> on Amazon Web Services, or equivalent offerings on other platforms such as <strong>Azure Communication Services<\/strong> (Email). If your team already routes all outbound email through SES, this bridge slots in naturally without standing up a separate SMTP relay.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">The bridge forwards each OKA notification as an email that includes both an HTML and a plain-text body: the HTML for mail clients that render it, the plain-text as a fallback for those that do not:<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6\">Python<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>ses.send_email(\r\n    Source=SES_FROM,\r\n    Destination={\"ToAddresses\": TO_LIST, \"CcAddresses\": CC_LIST},\r\n    Message={\r\n        \"Subject\": {\"Data\": subject, \"Charset\": \"UTF-8\"},\r\n        \"Body\": {\r\n            \"Text\": {\"Data\": _build_body_text(subject, body), \"Charset\": \"UTF-8\"},\r\n            \"Html\": {\"Data\": _build_body_html(subject, body), \"Charset\": \"UTF-8\"},\r\n        },\r\n    },\r\n)<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F6F6F4\">ses.send_email(<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">Source<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">SES_FROM<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">Destination<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">{<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">ToAddresses<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #BF9EEE\">TO_LIST<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">CcAddresses<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #BF9EEE\">CC_LIST<\/span><span style=\"color: #F6F6F4\">},<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #FFB86C; font-style: italic\">Message<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">{<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Subject<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Data<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: subject, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Charset<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">UTF-8<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">},<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">        <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Body<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: {<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Text<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Data<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: _build_body_text(subject, body), <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Charset<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">UTF-8<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">},<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">            <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Html<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: {<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Data<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: _build_body_html(subject, body), <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Charset<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">UTF-8<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">},<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">        },<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    },<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">)<\/span><\/span><\/code><\/pre><\/div>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><br>The HTML body includes an OKA-branded header, the alert subject as the email subject line, and the full alert body inside a <code>&lt;pre&gt;<\/code> block. The body text is HTML-escaped before insertion, so alert messages containing <code>&lt;<\/code>, <code>&gt;<\/code>, or <code>&amp;<\/code> characters cannot break the template.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">Authentication uses the standard boto3 credential chain: <code>AWS_ACCESS_KEY_ID<\/code> and <code>AWS_SECRET_ACCESS_KEY<\/code> via the <code>.env<\/code> file, or an IAM role attached to the EC2 instance running the bridge. The IAM role approach is recommended for production: no credentials in files, no rotation to manage.<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Variable<\/th><th>Required<\/th><th>Description<\/th><\/tr><\/thead><tbody><tr><td><code>SES_FROM_EMAIL<\/code><\/td><td>\u2713<\/td><td>Verified sender address in SES<\/td><\/tr><tr><td><code>SES_TO_EMAILS<\/code><\/td><td>\u2713<\/td><td>Comma-separated recipient list<\/td><\/tr><tr><td><code>SES_CC_EMAILS<\/code><\/td><td>&nbsp;<\/td><td>Optional CC addresses<\/td><\/tr><tr><td><code>AWS_SES_REGION<\/code><\/td><td>&nbsp;<\/td><td>SES region (default: <code>eu-west-1<\/code>)<\/td><\/tr><tr><td><code>AWS_ACCESS_KEY_ID<\/code> \/ <code>AWS_SECRET_ACCESS_KEY<\/code><\/td><td>&nbsp;<\/td><td>Optional when using an IAM role<\/td><\/tr><\/tbody><\/table><\/figure>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><strong>Why this matters.<\/strong> Slack reaches the operations team; email reaches everyone else. Finance wants to know when the cost-per-project alert fires. Management wants visibility on sustainability anomalies. Compliance may require a logged, timestamped record of every critical alert. The SES bridge makes OKA&#8217;s alerting reach as wide as your distribution list, at the cost of a single environment variable.<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"752\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES-1024x752.png\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES-1024x752.png\" alt=\"\" class=\"lazyload wp-image-3024\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271024%27%20height%3D%27752%27%20viewBox%3D%270%200%201024%20752%27%3E%3Crect%20width%3D%271024%27%20height%3D%27752%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES-200x147.png 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES-300x220.png 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES-400x294.png 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES-600x441.png 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES-768x564.png 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES-800x588.png 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES-1024x752.png 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/email-through-SES.png 1074w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\r\n\r\n\r\n\r\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">Making alerts trackable: the Jira bridge<\/h3>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">Not every alert needs to be acted on immediately, but every alert should be owned. The Jira bridge creates a <strong>Jira Cloud issue<\/strong> each time OKA fires a notification, turning an ephemeral alert into a tracked work item with a priority, an assignee, and a status: the natural lifecycle of an incident for teams that manage their operations backlog in Jira.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">The bridge extracts the alert level from the subject line and maps it to a Jira priority through a configurable mapping:<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6\">Python<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># Default mapping, overridable via JIRA_PRIORITY_MAP environment variable:\r\n# \"critical:Highest,high:High,medium:Medium,low:Low,ok:Low\"\r\nlevel    = _level_from_subject(subject)      # \"high\", \"critical\", \"medium\", \u2026\r\npriority = PRIORITY_MAP.get(level, \"Medium\")<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #7B7F8B\"># Default mapping, overridable via JIRA_PRIORITY_MAP environment variable:<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #7B7F8B\"># &quot;critical:Highest,high:High,medium:Medium,low:Low,ok:Low&quot;<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">level    <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> _level_from_subject(subject)      <\/span><span style=\"color: #7B7F8B\"># &quot;high&quot;, &quot;critical&quot;, &quot;medium&quot;, \u2026<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">priority <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">PRIORITY_MAP<\/span><span style=\"color: #F6F6F4\">.get(level, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">Medium<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span><\/code><\/pre><\/div>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><br>The issue description is formatted in <strong>Atlassian Document Format (ADF)<\/strong> (Jira Cloud&#8217;s structured rich-text format) with the full alert body in a code block, preserving every line of detail. Issues can optionally be auto-assigned to a specific Jira account and tagged with configurable labels, which is useful for filtering all OKA-generated issues in a project.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">A key feature is <strong>deduplication<\/strong>. A sustained anomaly that triggers OKA&#8217;s healthcheck every 15 minutes would otherwise create 96 Jira issues per day: noise that would quickly bury the signal. The bridge uses an in-memory TTL cache to suppress duplicates within a configurable window (default: 30 minutes):<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6\">Python<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># In-memory TTL cache: 256 slots, TTL from JIRA_DEDUP_TTL_MINUTES (default 30 min)\r\n_dedup_cache: TTLCache = TTLCache(maxsize=256, ttl=JIRA_DEDUP_TTL)\r\n\r\n# On each incoming notification \u2014 return early if already seen recently\r\nif subject in _dedup_cache:\r\n    return _dedup_cache&#91;subject&#93;   # return the cached issue URL, skip creation\r\n\r\n# ...create the Jira issue and register it for deduplication...\r\n_dedup_cache&#91;subject&#93; = issue_url<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #7B7F8B\"># In-memory TTL cache: 256 slots, TTL from JIRA_DEDUP_TTL_MINUTES (default 30 min)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">_dedup_cache: TTLCache <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> TTLCache(<\/span><span style=\"color: #FFB86C; font-style: italic\">maxsize<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">256<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">ttl<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #BF9EEE\">JIRA_DEDUP_TTL<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\r\n<span class=\"line\"><\/span>\r\n<span class=\"line\"><span style=\"color: #7B7F8B\"># On each incoming notification \u2014 return early if already seen recently<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> subject <\/span><span style=\"color: #F286C4\">in<\/span><span style=\"color: #F6F6F4\"> _dedup_cache:<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> _dedup_cache&#91;subject&#93;   <\/span><span style=\"color: #7B7F8B\"># return the cached issue URL, skip creation<\/span><\/span>\r\n<span class=\"line\"><\/span>\r\n<span class=\"line\"><span style=\"color: #7B7F8B\"># ...create the Jira issue and register it for deduplication...<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">_dedup_cache&#91;subject&#93; <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> issue_url<\/span><\/span><\/code><\/pre><\/div>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><br>The bridge routes requests through Atlassian&#8217;s <code>api.atlassian.com<\/code> gateway using a scoped API token with <code>write:jira-work<\/code> permission: the recommended approach for Jira Cloud integrations, distinct from classic token authentication. Required variables: <code>JIRA_CLOUD_ID<\/code> (your Atlassian cloud UUID), <code>JIRA_SITE_URL<\/code> (your organization&#8217;s Atlassian base URL), <code>JIRA_EMAIL<\/code>, <code>JIRA_API_TOKEN<\/code>, and <code>JIRA_PROJECT_KEY<\/code>.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><strong>Why this matters.<\/strong> Alerts that are seen but not tracked tend to be forgotten. Creating a Jira issue turns &#8220;we noticed a burst of job submission&#8221; into an assigned ticket with a due date and a visible audit trail. When the oncall engineer resolves the incident, the issue is closed; if the anomaly recurs after the dedup TTL expires, a new issue is created: maintaining a clean history of how often each healthcheck fires and how quickly each occurrence is resolved.<\/p>\r\n\r\n\r\n<div class=\"wp-block-image\">\r\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" width=\"1024\" height=\"516\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-1024x516.png\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-1024x516.png\" alt=\"\" class=\"lazyload wp-image-3025\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271024%27%20height%3D%27516%27%20viewBox%3D%270%200%201024%20516%27%3E%3Crect%20width%3D%271024%27%20height%3D%27516%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-200x101.png 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-300x151.png 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-400x202.png 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-540x272.png 540w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-600x303.png 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-768x387.png 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-800x404.png 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-1024x516.png 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-1200x605.png 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue-1536x775.png 1536w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/jira-issue.png 1707w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\r\n<\/div>\r\n\r\n\r\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">Closing the loop: the reporting bridge<\/h3>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">The three bridges above all push OKA notifications outward: to Slack channels, email inboxes, and Jira boards. The reporting bridge does something qualitatively different: it <strong>calls back into OKA itself<\/strong>, using OKA&#8217;s own reporting and notification infrastructure to respond to the alert. OKA is both the origin of the webhook and the source of the response.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">When a healthcheck alert fires, the bridge executes a four-step orchestration against the OKA REST API:<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6\">Python<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>def _handle_notification(subject: str, body: str):\r\n    # Extract the healthcheck name from \"&#91;OKA Healthcheck&#93; &lt;name>: &lt;Level>\"\r\n    hc_name = _hc_name_from_subject(subject)\r\n\r\n    # Step 1 \u2014 find the healthcheck in OKA to get the workload it monitors\r\n    workload_id = _find_healthcheck_workload(hc_name)\r\n\r\n    # Step 2 \u2014 find the notification profile that will handle PDF email delivery\r\n    profile_id = _find_notification_profile_id(OKA_NOTIFICATION_PROFILE)\r\n\r\n    # Step 3 \u2014 create a report scoped to that workload, with the alert body as description\r\n    report_id = _create_report(workload_id, hc_name, body, profile_id)\r\n\r\n    # Step 4 \u2014 trigger execution; OKA generates the PDF asynchronously and emails it\r\n    _run_report(report_id)<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F286C4\">def<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #62E884\">_handle_notification<\/span><span style=\"color: #F6F6F4\">(<\/span><span style=\"color: #FFB86C; font-style: italic\">subject<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #FFB86C; font-style: italic\">body<\/span><span style=\"color: #F286C4\">:<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #97E1F1; font-style: italic\">str<\/span><span style=\"color: #F6F6F4\">):<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># Extract the healthcheck name from &quot;&#91;OKA Healthcheck&#93; &lt;name&gt;: &lt;Level&gt;&quot;<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    hc_name <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> _hc_name_from_subject(subject)<\/span><\/span>\r\n<span class=\"line\"><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># Step 1 \u2014 find the healthcheck in OKA to get the workload it monitors<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    workload_id <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> _find_healthcheck_workload(hc_name)<\/span><\/span>\r\n<span class=\"line\"><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># Step 2 \u2014 find the notification profile that will handle PDF email delivery<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    profile_id <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> _find_notification_profile_id(<\/span><span style=\"color: #BF9EEE\">OKA_NOTIFICATION_PROFILE<\/span><span style=\"color: #F6F6F4\">)<\/span><\/span>\r\n<span class=\"line\"><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># Step 3 \u2014 create a report scoped to that workload, with the alert body as description<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    report_id <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> _create_report(workload_id, hc_name, body, profile_id)<\/span><\/span>\r\n<span class=\"line\"><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #7B7F8B\"># Step 4 \u2014 trigger execution; OKA generates the PDF asynchronously and emails it<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    _run_report(report_id)<\/span><\/span><\/code><\/pre><\/div>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><br>The report is created with two default pages: <strong>KPI<\/strong> (key performance indicators for the workload) and <strong>State<\/strong> (the current utilization breakdown). The full alert body is embedded as the report description, so the context of the anomaly is visible inside the document alongside the dashboard data. OKA then generates the PDF via its internal task queue and emails it through the configured notification profile.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">The result: by the time the on-call engineer opens the Jira ticket or the alert email, the full report is already in their inbox. Not just a notification that something is wrong, but a rendered document showing what the affected workload looks like right now: the same dashboards they would consult manually anyway, delivered automatically.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">Because the bridge must authenticate to the OKA API on every request, a module-level JWT token cache avoids re-authenticating unnecessarily:<\/p>\r\n\r\n\r\n\r\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6\">Python<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#f6f6f4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly># OKA issues 24-hour JWT tokens; refresh 60 s before expiry to avoid race conditions\r\nif _token_cache&#91;\"token\"&#93; and now &lt; _token_cache&#91;\"expires_at\"&#93;:\r\n    return _token_cache&#91;\"token\"&#93;   # reuse the cached token\r\n\r\nr = httpx.post(f\"{OKA_BASE_URL}\/api\/auth\/\",\r\n               json={\"username\": OKA_USERNAME, \"password\": OKA_PASSWORD})\r\ntoken = r.json()&#91;\"access\"&#93;\r\n_token_cache.update({\"token\": token, \"expires_at\": now + 86400 - 60})<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dracula-soft\" style=\"background-color: #282A36\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #7B7F8B\"># OKA issues 24-hour JWT tokens; refresh 60 s before expiry to avoid race conditions<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F286C4\">if<\/span><span style=\"color: #F6F6F4\"> _token_cache&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">token<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93; <\/span><span style=\"color: #F286C4\">and<\/span><span style=\"color: #F6F6F4\"> now <\/span><span style=\"color: #F286C4\">&lt;<\/span><span style=\"color: #F6F6F4\"> _token_cache&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">expires_at<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;:<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">    <\/span><span style=\"color: #F286C4\">return<\/span><span style=\"color: #F6F6F4\"> _token_cache&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">token<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;   <\/span><span style=\"color: #7B7F8B\"># reuse the cached token<\/span><\/span>\r\n<span class=\"line\"><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">r <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> httpx.post(<\/span><span style=\"color: #F286C4\">f<\/span><span style=\"color: #E7EE98\">&quot;<\/span><span style=\"color: #BF9EEE\">{OKA_BASE_URL}<\/span><span style=\"color: #E7EE98\">\/api\/auth\/&quot;<\/span><span style=\"color: #F6F6F4\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">               <\/span><span style=\"color: #FFB86C; font-style: italic\">json<\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\">{<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">username<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #BF9EEE\">OKA_USERNAME<\/span><span style=\"color: #F6F6F4\">, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">password<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: <\/span><span style=\"color: #BF9EEE\">OKA_PASSWORD<\/span><span style=\"color: #F6F6F4\">})<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">token <\/span><span style=\"color: #F286C4\">=<\/span><span style=\"color: #F6F6F4\"> r.json()&#91;<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">access<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">&#93;<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #F6F6F4\">_token_cache.update({<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">token<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: token, <\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #E7EE98\">expires_at<\/span><span style=\"color: #DEE492\">&quot;<\/span><span style=\"color: #F6F6F4\">: now <\/span><span style=\"color: #F286C4\">+<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">86400<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #F286C4\">-<\/span><span style=\"color: #F6F6F4\"> <\/span><span style=\"color: #BF9EEE\">60<\/span><span style=\"color: #F6F6F4\">})<\/span><\/span><\/code><\/pre><\/div>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><br>The 60-second safety margin prevents multiple gunicorn workers from simultaneously discovering an expired token under concurrent load.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">Configuration is minimal: <code>OKA_BASE_URL<\/code>, <code>OKA_USERNAME<\/code>, <code>OKA_PASSWORD<\/code> (a service account with report creation rights), and <code>OKA_NOTIFICATION_PROFILE<\/code> (the exact name of the OKA notification profile that will handle the PDF email delivery).<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\"><strong>Why this matters.<\/strong> This bridge turns OKA&#8217;s alerting into a self-service diagnostics tool. A healthcheck fires, and within seconds (without any manual intervention) the relevant stakeholder has a PDF report covering the KPI and State of the workload that triggered the alarm. Context is delivered alongside the notification, not as a follow-up requiring manual navigation. For HPC operations teams that spend time locating the right dashboard after every alert, this closes the loop automatically.<\/p>\r\n\r\n\r\n\r\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"302\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-1024x302.png\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-1024x302.png\" alt=\"\" class=\"lazyload wp-image-3026\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271024%27%20height%3D%27302%27%20viewBox%3D%270%200%201024%20302%27%3E%3Crect%20width%3D%271024%27%20height%3D%27302%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-200x59.png 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-300x88.png 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-400x118.png 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-600x177.png 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-768x226.png 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-800x236.png 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-1024x302.png 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-1200x353.png 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated-1536x452.png 1536w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/OKA-reporting-generated.png 1718w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\r\n\r\n\r\n\r\n<div style=\"height:50px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Conclusion: complete the circuit<\/h2>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">Data Enhancers, Healthcheck Rules, and Webhook Bridges form a complete observability circuit for an HPC cluster:<\/p>\r\n\r\n\r\n\r\n<ul class=\"wp-block-list\">\r\n<li><strong>Data Enhancers<\/strong> transform raw scheduler output into meaningful metrics: energy per job, carbon footprint per project, cost per user.<\/li>\r\n\r\n\r\n\r\n<li><strong>Healthcheck Rules<\/strong> monitor those metrics on a schedule and fire structured alerts when anomalies or threshold violations are detected.<\/li>\r\n\r\n\r\n\r\n<li><strong>Webhook Bridges<\/strong> route those alerts to the tools where action is taken: Slack channels, email inboxes, Jira backlogs, and automated reports.<\/li>\r\n<\/ul>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">Each bridge is independent. You can deploy just the Slack bridge today, add the Jira bridge when your team adopts incident tracking, and connect the reporting bridge once you want context delivered automatically alongside every alert.<\/p>\r\n\r\n\r\n<div class=\"wp-block-image\">\r\n<figure class=\"aligncenter size-full\"><img decoding=\"async\" width=\"724\" height=\"433\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-config.png\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-config.png\" alt=\"\" class=\"lazyload wp-image-3028\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27724%27%20height%3D%27433%27%20viewBox%3D%270%200%20724%20433%27%3E%3Crect%20width%3D%27724%27%20height%3D%27433%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-config-200x120.png 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-config-300x179.png 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-config-400x239.png 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-config-600x359.png 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-config.png 724w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 724px) 100vw, 724px\" \/><\/figure>\r\n<\/div>\r\n\r\n\r\n<p class=\"wp-block-paragraph\">Getting started with any bridge follows the same pattern:<\/p>\r\n\r\n\r\n\r\n<ol class=\"wp-block-list\">\r\n<li><strong>Clone the repository<\/strong> and <code>cd<\/code> into the bridge subdirectory you want to deploy.<\/li>\r\n\r\n\r\n\r\n<li><strong>Run <code>uv sync<\/code><\/strong> to create the virtual environment and install dependencies.<\/li>\r\n\r\n\r\n\r\n<li><strong>Copy <code>.env.example<\/code> to <code>.env<\/code><\/strong> and fill in the required variables.<\/li>\r\n\r\n\r\n\r\n<li><strong>Install the systemd service<\/strong> using the provided <code>.service<\/code> file.<\/li>\r\n\r\n\r\n\r\n<li><strong>Create a Notification Profile in OKA<\/strong> pointing to <code>http:\/\/&lt;your-host&gt;:&lt;port&gt;\/notify<\/code>.<\/li>\r\n\r\n\r\n\r\n<li><strong>Trigger a test alert<\/strong> from the OKA Healthcheck panel and verify delivery.<\/li>\r\n<\/ol>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">If you build a bridge for a tool not yet covered (PagerDuty, Microsoft Teams, Amazon SNS, a custom ITSM platform) consider contributing it back to the repository. The collection grows through the collective experience of teams running real clusters.<\/p>\r\n\r\n\r\n\r\n<p class=\"wp-block-paragraph\">OKA tells you what is happening on your cluster. Webhook bridges make sure the right people know about it, wherever they are.<\/p>\r\n\r\n\r\n\r\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\r\n<p class=\"wp-block-paragraph\">Questions, feature requests, or contributions? Open an issue or a pull request in the <a href=\"https:\/\/bitbucket.org\/ucit\/oka_webhooks\" target=\"_blank\" rel=\"noreferrer noopener\"><code>oka_webhooks<\/code> repository<\/a><strong><mark style=\"background-color: rgba(0, 0, 0, 0);\" class=\"has-inline-color has-vivid-cyan-blue-color\"> (coming soon!)<\/mark><\/strong>, or <a href=\"\/#contact\" type=\"link\" id=\"\/contact\">contact the UCit team<\/a>.<\/p>\r\n<\/blockquote>","protected":false},"excerpt":{"rendered":"<p>From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges An alert that fires<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38],"tags":[],"class_list":["post-3010","post","type-post","status-publish","format-standard","hentry","category-use-case"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges - OKA by UCit<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges - OKA by UCit\" \/>\n<meta property=\"og:description\" content=\"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges An alert that fires\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/\" \/>\n<meta property=\"og:site_name\" content=\"OKA by UCit\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-22T06:54:51+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-22T07:06:34+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1718\" \/>\n\t<meta property=\"og:image:height\" content=\"574\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"okaadmin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"okaadmin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/\"},\"author\":{\"name\":\"okaadmin\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#\\\/schema\\\/person\\\/884ebbecc2bba75633dbba352cf7860d\"},\"headline\":\"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges\",\"datePublished\":\"2026-06-22T06:54:51+00:00\",\"dateModified\":\"2026-06-22T07:06:34+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/\"},\"wordCount\":2119,\"image\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/oka.how\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/webhook-list-1024x342.png\",\"articleSection\":[\"Use-case\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/\",\"url\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/\",\"name\":\"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges - OKA by UCit\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/oka.how\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/webhook-list-1024x342.png\",\"datePublished\":\"2026-06-22T06:54:51+00:00\",\"dateModified\":\"2026-06-22T07:06:34+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#\\\/schema\\\/person\\\/884ebbecc2bba75633dbba352cf7860d\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[[\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/\"]]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/#primaryimage\",\"url\":\"https:\\\/\\\/oka.how\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/webhook-list.png\",\"contentUrl\":\"https:\\\/\\\/oka.how\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/webhook-list.png\",\"width\":1718,\"height\":574},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/06\\\/22\\\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/oka.how\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/\",\"name\":\"OKA by UCit\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#\\\/schema\\\/person\\\/884ebbecc2bba75633dbba352cf7860d\",\"name\":\"okaadmin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g\",\"caption\":\"okaadmin\"},\"sameAs\":[\"https:\\\/\\\/oka.how\"],\"url\":\"https:\\\/\\\/oka.how\\\/index.php\\\/author\\\/author-1\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges - OKA by UCit","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/","og_locale":"en_US","og_type":"article","og_title":"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges - OKA by UCit","og_description":"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges An alert that fires","og_url":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/","og_site_name":"OKA by UCit","article_published_time":"2026-06-22T06:54:51+00:00","article_modified_time":"2026-06-22T07:06:34+00:00","og_image":[{"width":1718,"height":574,"url":"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list.png","type":"image\/png"}],"author":"okaadmin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"okaadmin","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/#article","isPartOf":{"@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/"},"author":{"name":"okaadmin","@id":"https:\/\/oka.how\/index.php\/en\/#\/schema\/person\/884ebbecc2bba75633dbba352cf7860d"},"headline":"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges","datePublished":"2026-06-22T06:54:51+00:00","dateModified":"2026-06-22T07:06:34+00:00","mainEntityOfPage":{"@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/"},"wordCount":2119,"image":{"@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/#primaryimage"},"thumbnailUrl":"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-1024x342.png","articleSection":["Use-case"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/","url":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/","name":"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges - OKA by UCit","isPartOf":{"@id":"https:\/\/oka.how\/index.php\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/#primaryimage"},"image":{"@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/#primaryimage"},"thumbnailUrl":"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list-1024x342.png","datePublished":"2026-06-22T06:54:51+00:00","dateModified":"2026-06-22T07:06:34+00:00","author":{"@id":"https:\/\/oka.how\/index.php\/en\/#\/schema\/person\/884ebbecc2bba75633dbba352cf7860d"},"breadcrumb":{"@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":[["https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/"]]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/#primaryimage","url":"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list.png","contentUrl":"https:\/\/oka.how\/wp-content\/uploads\/2026\/06\/webhook-list.png","width":1718,"height":574},{"@type":"BreadcrumbList","@id":"https:\/\/oka.how\/index.php\/2026\/06\/22\/from-alert-to-action-connecting-oka-to-your-ops-toolchain-with-open-source-webhook-bridges\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oka.how\/"},{"@type":"ListItem","position":2,"name":"From alert to action: connecting OKA to your ops toolchain with open-source webhook bridges"}]},{"@type":"WebSite","@id":"https:\/\/oka.how\/index.php\/en\/#website","url":"https:\/\/oka.how\/index.php\/en\/","name":"OKA by UCit","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/oka.how\/index.php\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/oka.how\/index.php\/en\/#\/schema\/person\/884ebbecc2bba75633dbba352cf7860d","name":"okaadmin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g","caption":"okaadmin"},"sameAs":["https:\/\/oka.how"],"url":"https:\/\/oka.how\/index.php\/author\/author-1\/"}]}},"_links":{"self":[{"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/posts\/3010","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/comments?post=3010"}],"version-history":[{"count":5,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/posts\/3010\/revisions"}],"predecessor-version":[{"id":3036,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/posts\/3010\/revisions\/3036"}],"wp:attachment":[{"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/media?parent=3010"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/categories?post=3010"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/tags?post=3010"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}