MS Teams Adaptive Card Support (since MS Teams connectors are being deprecated)

Hi Team,

I went to do a webhook integration with MS teams, and it looks like they’re deprecating the existing webhook integration through connectors and replacing with PowerBI Workflows. Unfortunately, since Workflows seems to use adaptive cards and not message cards, I have been unable to get the new Workflows app to accept the incoming webhook. It works in a different JSON structure than what the existing Teams Webhook integration LibreNMS uses. They are removing functionality to add connectors on Aug. 15, and deprecating the feature all together in October. I attached a screenshot below of the reason the webhook fails. I’d like to request an update to the MS Teams alert transport to accommodate the change in webhook integration with Teams.

Huge thanks to the very active Dev team and community that keeps LibreNMS running. I’ve used it extensively for years now :smiley:

  • Andrew

Hi There, I have the same issue, did you find a fix ?

I have not. A new Alert Template has to be made with the correct JSON structure and information for an Adaptive card. Looked at the file for the existing template (under /opt/librenms/librenms/alert/transport/msteams.php), and it is setup to use a message card, which are not supported by PowerBI Workflows. I’m not a developer, so I’m going to wait till the LibreNMS team adds that to an update :slight_smile:

Just chiming in here to say that Microsoft recently announced that they are extending the deadline for Teams connectors until December 2025 (with some caveats it seems). No rush to move away from webhooks just yet, i’m sure the devs can get adaptive cards working by then.

That’s not quite true, existing webhooks will work but you won’t be able to make any new ones from 15 August. Vote here to register your dissatisfaction with this deprecation without a suitable replacement:

https://feedbackportal.microsoft.com/feedback/idea/80ed6877-b642-ef11-b4ad-000d3a7aba8b

Hi guys we used to fix this issue like replacing the code in
/opt/librenms/LibreNMS/Alert/Transport/Msteams.php

<?php
/*
 * LibreNMS - Microsoft Teams Logic Apps Integration
 */

namespace LibreNMS\Alert\Transport;

use LibreNMS\Alert\Transport;
use LibreNMS\Exceptions\AlertTransportDeliveryException;

class Msteams extends Transport
{
    protected string $name = 'Microsoft Teams via Logic Apps';

    public function deliverAlert(array $alert_data): bool
    {
        $webhook_url = $this->config['msteam-url'];

        // Construct an Adaptive Card payload
        $data = [
            "type" => "message",
            "attachments" => [
                [
                    "contentType" => "application/vnd.microsoft.card.adaptive",
                    "content" => [
                        "schema" => "http://adaptivecards.io/schemas/adaptive-card.json",
                        "version" => "1.4",
                        "type" => "AdaptiveCard",
                        "body" => [
                            [
                                "type" => "TextBlock",
                                "size" => "Medium",
                                "weight" => "Bolder",
                                "text" => "🚨 LibreNMS Alert"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**Alert:** " . $alert_data['title'],
                                "wrap" => true
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**State:** " . $alert_data['state'],
                                "wrap" => true
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**Message:** " . strip_tags($alert_data['msg']),
                                "wrap" => true
                            ]
                        ]
                    ]
                ]
            ]
        ];

        $payload = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        $ch = curl_init($webhook_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Accept: application/json'
        ]);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($http_code == 200) {
            return true;
        }

        throw new AlertTransportDeliveryException($alert_data, $http_code, $response, json_encode($data, JSON_PRETTY_PRINT));
    }

    public static function configTemplate(): array
    {
        return [
            'config' => [
                [
                    'title' => 'Logic Apps URL',
                    'name' => 'msteam-url',
                    'descr' => 'Microsoft Logic Apps Trigger URL',
                    'type' => 'text',
                ],
            ],
            'validation' => [
                'msteam-url' => 'required|url',
            ],
        ];
    }
}

and some simplifed and more colorfull version

<?php
/*
 * LibreNMS - Microsoft Teams Logic Apps Integration
 */

namespace LibreNMS\Alert\Transport;

use LibreNMS\Alert\Transport;
use LibreNMS\Exceptions\AlertTransportDeliveryException;

class Msteams extends Transport
{
    protected string $name = 'Microsoft Teams via Logic Apps';

    public function deliverAlert(array $alert_data): bool
    {
        $webhook_url = $this->config['msteam-url'];

        // Define alert color based on severity
        $severity_color = match (strtolower($alert_data['severity'] ?? '')) {
            'critical' => "#FF0000",  // Red for critical
            'warning' => "#FFA500",   // Orange for warning
            default => "#008000"      // Green for normal
        };

        // Convert "State" from number to meaningful status
        $stateText = match ($alert_data['state']) {
            0 => "OK ✅",
            1 => "Warning ⚠️",
            2 => "Critical ❌",
            default => "Unknown"
        };

        // Clean Fault Details - Only extract useful information
        if (!empty($alert_data['faults']) && is_array($alert_data['faults'])) {
            $faultDetails = array_map(function ($fault) {
                return "- " . ($fault['sysDescr'] ?? "Unknown Fault");
            }, $alert_data['faults']);

            $faultDetails = implode("\n", $faultDetails);
        } else {
            $faultDetails = "No additional fault details available.";
        }

        // Construct an Adaptive Card payload with improved formatting
        $data = [
            "type" => "message",
            "attachments" => [
                [
                    "contentType" => "application/vnd.microsoft.card.adaptive",
                    "content" => [
                        "$schema" => "http://adaptivecards.io/schemas/adaptive-card.json",
                        "version" => "1.4",
                        "type" => "AdaptiveCard",
                        "body" => [
                            [
                                "type" => "TextBlock",
                                "size" => "Large",
                                "weight" => "Bolder",
                                "color" => "Attention",
                                "text" => "🚨 **LibreNMS Alert**",
                                "horizontalAlignment" => "Center"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**🔔 Alert:** " . $alert_data['title'],
                                "wrap" => true,
                                "weight" => "Bolder",
                                "color" => "Good"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**📌 State:** " . $stateText,
                                "wrap" => true,
                                "color" => "Accent"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**📅 Timestamp:** " . date("Y-m-d H:i:s"),
                                "wrap" => true
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**🆔 Unique-ID:** " . $alert_data['uid'],
                                "wrap" => true
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**⚠️ Severity:** " . ucfirst($alert_data['severity']),
                                "wrap" => true,
                                "color" => "Attention"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**🔍 Fault Details:**",
                                "wrap" => true,
                                "size" => "Medium",
                                "weight" => "Bolder"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => $faultDetails,
                                "wrap" => true
                            ]
                        ]
                    ]
                ]
            ]
        ];

        $payload = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        $ch = curl_init($webhook_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Accept: application/json'
        ]);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($http_code == 200) {
            return true;
        }

        throw new AlertTransportDeliveryException($alert_data, $http_code, $response, json_encode($data, JSON_PRETTY_PRINT));
    }

    public static function configTemplate(): array
    {
        return [
            'config' => [
                [
                    'title' => 'Logic Apps URL',
                    'name' => 'msteam-url',
                    'descr' => 'Microsoft Logic Apps Trigger URL',
                    'type' => 'text',
                ],
            ],
            'validation' => [
                'msteam-url' => 'required|url',
            ],
        ];
    }
}

and version with fixed huge Fault Details message

<?php
/*
 * LibreNMS - Microsoft Teams Logic Apps Integration
 */

namespace LibreNMS\Alert\Transport;

use LibreNMS\Alert\Transport;
use LibreNMS\Exceptions\AlertTransportDeliveryException;

class Msteams extends Transport
{
    protected string $name = 'Microsoft Teams via Logic Apps';

    public function deliverAlert(array $alert_data): bool
    {
        $webhook_url = $this->config['msteam-url'];

        // Define alert color based on severity
        $severity_color = match (strtolower($alert_data['severity'] ?? '')) {
            'critical' => "#FF0000",  // Red for critical
            'warning' => "#FFA500",   // Orange for warning
            default => "#008000"      // Green for normal
        };

        // Convert "State" from number to meaningful status
        $stateText = match ($alert_data['state']) {
            0 => "OK ✅",
            1 => "Warning ⚠️",
            2 => "Critical ❌",
            default => "Unknown"
        };

        // Limit the number of fault details shown
        $maxFaults = 5;
        $faultCount = count($alert_data['faults'] ?? []);

        if (!empty($alert_data['faults']) && is_array($alert_data['faults'])) {
            $faultDetails = array_slice($alert_data['faults'], 0, $maxFaults);
            $faultDetails = array_map(function ($fault) {
                return "- " . ($fault['sysDescr'] ?? "Unknown Fault");
            }, $faultDetails);

            $faultDetailsText = implode("\n", $faultDetails);

            if ($faultCount > $maxFaults) {
                $faultDetailsText .= "\n...and " . ($faultCount - $maxFaults) . " more.";
            }
        } else {
            $faultDetailsText = "No additional fault details available.";
        }

        // Construct an Adaptive Card payload with compact formatting
        $data = [
            "type" => "message",
            "attachments" => [
                [
                    "contentType" => "application/vnd.microsoft.card.adaptive",
                    "content" => [
                        "$schema" => "http://adaptivecards.io/schemas/adaptive-card.json",
                        "version" => "1.4",
                        "type" => "AdaptiveCard",
                        "body" => [
                            [
                                "type" => "TextBlock",
                                "size" => "Large",
                                "weight" => "Bolder",
                                "color" => "Attention",
                                "text" => "🚨 **LibreNMS Alert**",
                                "horizontalAlignment" => "Center",
                                "spacing" => "Small"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**🔔 Alert:** " . $alert_data['title'],
                                "wrap" => true,
                                "weight" => "Bolder",
                                "color" => "Good",
                                "spacing" => "Small"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**📌 State:** " . $stateText,
                                "wrap" => true,
                                "color" => "Accent",
                                "spacing" => "Small"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**📅 Timestamp:** " . date("Y-m-d H:i:s"),
                                "wrap" => true,
                                "spacing" => "Small"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**🆔 Unique-ID:** " . $alert_data['uid'],
                                "wrap" => true,
                                "spacing" => "Small"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**⚠️ Severity:** " . ucfirst($alert_data['severity']),
                                "wrap" => true,
                                "color" => "Attention",
                                "spacing" => "Small"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => "**🔍 Fault Details:**",
                                "wrap" => true,
                                "size" => "Medium",
                                "weight" => "Bolder",
                                "spacing" => "Small"
                            ],
                            [
                                "type" => "TextBlock",
                                "text" => $faultDetailsText,
                                "wrap" => true,
                                "spacing" => "Small"
                            ]
                        ]
                    ]
                ]
            ]
        ];

        $payload = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        $ch = curl_init($webhook_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Accept: application/json'
        ]);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($http_code == 200) {
            return true;
        }

        throw new AlertTransportDeliveryException($alert_data, $http_code, $response, json_encode($data, JSON_PRETTY_PRINT));
    }

    public static function configTemplate(): array
    {
        return [
            'config' => [
                [
                    'title' => 'Logic Apps URL',
                    'name' => 'msteam-url',
                    'descr' => 'Microsoft Logic Apps Trigger URL',
                    'type' => 'text',
                ],
            ],
            'validation' => [
                'msteam-url' => 'required|url',
            ],
        ];
    }
}