How to Build an AI On-Call Triage Workflow from Custom HMAC Alert Webhooks

Accept signed alert payloads from your monitoring stack over a Custom-scheme HMAC webhook, classify severity and a likely cause with a Connector node in Agent mode, branch on severity with a Condition node, page your on-call team in Slack, and archive every alert in MongoDB.

What This Integration Does

Monitoring tools like Prometheus Alertmanager, Grafana, Datadog, and most homegrown health checks can fire an outbound webhook when something breaks. The problem is volume and noise: a flapping disk or a single 500 looks the same shape as a full outage, and a human still has to read each payload, decide if it is worth waking someone, and write it down somewhere. This Spojit workflow does that triage step for you. It receives the raw alert, uses an Agent-mode Connector node to read the payload and decide how serious it is and what most likely caused it, then only escalates the items that genuinely warrant a page while keeping a permanent record of everything.

The workflow is triggered by an HTTP POST from your alerting system to the workflow's webhook URL. Each request is verified with an HMAC signature using a Custom-scheme signing connection, so unsigned or tampered payloads are rejected before any AI cost is incurred. A Connector node in Agent mode classifies the alert into a structured result using a Response Schema, a Condition node routes on the returned severity, high-severity alerts post to your on-call Slack channel with send-message, and every alert (regardless of severity) is written to MongoDB with insert-documents so you have a searchable history. The workflow is stateless between runs: each alert produces one execution, one Slack message at most, and one stored document. Replayed alerts simply create another record unless you add the optional dedup described below.

Prerequisites

  • A monitoring system that can send an outbound HTTP POST webhook with a JSON body and an HMAC signature header.
  • A Slack connection added in Spojit, with access to your on-call channel. You will need the channel's ID (for example C0123ABCD), not just its name.
  • A MongoDB connection added in Spojit, pointing at the database where you want to keep alert history, with a target collection such as alerts.
  • A shared HMAC secret that both your monitoring system and Spojit will use to sign and verify each payload.
  • Familiarity with the structure of the alert payload your tool sends, so you can map fields into the classification prompt and the stored document.

Step 1: Add a Webhook trigger with a Custom HMAC signing connection

Create a new workflow and set its Trigger node type to Webhook. The Webhook trigger gives the workflow a unique URL that accepts an external HTTP POST and returns 202 with an executionId. To stop anyone from forging alerts, attach a signing connection and choose the Custom scheme. The Custom scheme lets you specify which header carries the signature and the shared secret used to compute it, so it works with monitoring tools that do not follow the Slack, GitHub, or Shopify conventions.

Configure your monitoring system to compute an HMAC-SHA256 of the exact request body using the same shared secret and send it in the header your signing connection expects (commonly X-Signature or X-Hub-Signature-256). Spojit recomputes the signature over the received body and rejects the request if it does not match, before any node runs. The trigger output is the parsed JSON body, available downstream as {{ input }}. A typical incoming payload looks like this:

{
  "alertname": "HighErrorRate",
  "service": "checkout-api",
  "summary": "5xx rate above 5% for 10m",
  "description": "checkout-api returned 512 5xx responses in the last 10 minutes",
  "value": "0.072",
  "startsAt": "2026-06-21T14:03:00Z",
  "labels": { "env": "production", "team": "payments" }
}

For details on the signing schemes, see the guide on setting up a Webhook trigger and the reference on setting up a Webhook connection.

Step 2: Classify the alert with a Connector node in Agent mode

Add a Connector node and switch it to Agent mode. Agent mode lets the agent read the alert and reason about it instead of you hand-coding rules for every alert shape. In the node's prompt, hand it the trigger fields and ask for a triage decision. Be explicit so the output is consistent:

You are triaging an infrastructure alert. Read this payload and decide
its severity and the single most likely cause.

Alert name: {{ input.alertname }}
Service: {{ input.service }}
Environment: {{ input.labels.env }}
Summary: {{ input.summary }}
Description: {{ input.description }}
Measured value: {{ input.value }}

Severity must be one of: critical, high, medium, low.
Treat production-facing customer impact as higher severity.

Agent mode costs AI credits and is the right tool here because the judgment (is this customer-facing? is it transient?) is exactly what you would otherwise pay a human to make at 3am. Keep the prompt focused on classification only; the routing and storage happen in later, deterministic nodes.

Step 3: Force structured output with a Response Schema

Still on the Agent-mode Connector node, define a Response Schema so the result is reliable JSON your Condition node can branch on, rather than free text. Without a schema, you cannot trust that a field called severity will always be present and lowercase. Use a schema like this:

{
  "type": "object",
  "properties": {
    "severity":   { "type": "string", "enum": ["critical", "high", "medium", "low"] },
    "likelyCause":{ "type": "string" },
    "summary":    { "type": "string" },
    "shouldPage": { "type": "boolean" }
  },
  "required": ["severity", "likelyCause", "summary", "shouldPage"]
}

Set the node's output variable name to triage. Downstream you can now reference {{ triage.severity }}, {{ triage.likelyCause }}, {{ triage.summary }}, and {{ triage.shouldPage }} with confidence. The enum on severity guarantees the value matches one of the four labels you branch on in the next step. For more on this pattern, see using Structured Output for reliable AI data extraction.

Step 4: Branch on severity with a Condition node

Add a Condition node after the classification step. Configure its test to check the structured severity, for example: severity {{ triage.severity }} equals critical OR equals high. Send the true branch toward the Slack page (Step 5) and let the false branch skip straight to storage. This is the gate that keeps low and medium noise out of your on-call channel while still recording it.

Because the Connector node also returns shouldPage, you can instead branch on {{ triage.shouldPage }} is true if you would rather let the agent make the page/no-page call directly. Pick one approach and keep it consistent so the routing is easy to reason about. See using Condition nodes for the full comparison and operator reference.

Step 5: Page on-call by posting to Slack with send-message

On the true branch, add a Connector node on the Slack connector in Direct mode and select the send-message tool. Direct mode is correct here because the action is a single, predictable call with no judgment required. Map the inputs:

  • channel: your on-call channel ID, for example C0123ABCD.
  • text: a templated summary built from the triage result and the original alert, for example: [{{ triage.severity }}] {{ input.service }} - {{ triage.summary }}. Likely cause: {{ triage.likelyCause }}.

The text field doubles as the notification fallback, so keep the most important detail (service and severity) at the front. If you want richer formatting later, the same tool accepts an optional blocks array for Block Kit layout, but plain text is enough to page the team. For a deeper walkthrough of Slack alerting patterns, see sending Slack alerts for new events.

Step 6: Archive every alert in MongoDB with insert-documents

After the Condition node (joining both branches so every alert is recorded), add a Connector node on the MongoDB connector in Direct mode and select the insert-documents tool. Set collection to alerts and pass a single document in the documents array that combines the raw alert with the triage decision:

{
  "alertname": "{{ input.alertname }}",
  "service": "{{ input.service }}",
  "env": "{{ input.labels.env }}",
  "severity": "{{ triage.severity }}",
  "likelyCause": "{{ triage.likelyCause }}",
  "paged": "{{ triage.shouldPage }}",
  "rawSummary": "{{ input.summary }}",
  "receivedAt": "{{ input.startsAt }}"
}

The tool returns insertedCount and the generated insertedIds, confirming the write. Because both the true and false branches flow into this node, your alerts collection becomes a complete, queryable history: you can later run find-documents or aggregate to count alerts per service, measure how often you paged, or feed a dashboard. See the MongoDB connector reference for the full tool catalog.

Tips

  • Use a Manual trigger copy of the same logic while building, so you can paste sample payloads into the Run body without exposing the webhook URL. Switch the trigger to Webhook only once the classification looks right.
  • Keep the Agent-mode prompt deterministic: name the exact severity labels and define what counts as customer-facing. The tighter the prompt, the more stable the routing.
  • Store severity and service as top-level fields in MongoDB (as shown) so later find-documents filters and aggregate group stages stay simple and fast.
  • If a single alerting tool sends many similar events, ask Miraxa in the chat to "add a Condition node that checks if {{ triage.severity }} equals critical" to scaffold extra routing, then fine-tune it in the properties panel.

Common Pitfalls

  • Signature computed over the wrong body. HMAC must be calculated over the exact bytes you POST. If your monitoring tool pretty-prints or reorders JSON after signing, verification fails. Sign and send the identical body.
  • Using a channel name instead of an ID. The Slack send-message tool expects a channel ID in channel (for example C0123ABCD). Passing #on-call will not resolve.
  • Branching on free text. If you skip the Response Schema, the AI output may phrase severity differently each run ("High", "high priority"), and the Condition node will misroute. Always force the enum schema and branch on the structured field.
  • Duplicate records on retried webhooks. Many alerting tools retry on a slow response. Each retry creates another execution and another MongoDB document. Enable the Webhook trigger's opt-in dedup using your tool's event-id header, or include a stable alert id in the stored document so you can de-duplicate on read.

Testing

Validate end to end before pointing production traffic at it. First, with a temporary Manual trigger, paste a sample critical payload into the Run body and confirm the Connector node returns a valid triage object, the Condition node takes the true branch, a message lands in a private test Slack channel, and a document appears in your alerts collection. Then send a low payload and confirm it skips Slack but is still stored. Switch the trigger to Webhook, send one real signed alert from your monitoring tool's test feature, and check the execution history shows the request was accepted and verified. Only after the signed test passes should you wire the live alert routes. Review the run in the execution log to confirm each step's input and output as described in understanding execution logs.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.