Skip to main content

Send a Microsoft Teams Notification from a Workflow

The problem​

Some workflow results need a human to act on them: an order that couldn't be exported, a stock level below its minimum, a nightly job that found unexpected data. Email works, but many teams coordinate in Microsoft Teams, and a message in the channel where they already work gets seen faster.

The solution​

Teams can accept messages from outside through a webhook: a special URL that posts whatever is sent to it into a channel or chat. A Vince Platform workflow can call that URL with a REST API step:

After the earlier steps, a Transform builds the Adaptive Card message and a REST API step posts it to the Teams webhook.

The Transform step builds the message as an Adaptive Card, Microsoft's JSON format for rich messages with headings, lists of facts and buttons. The REST API step sends it.

Office 365 connectors are retired

Older guides use an "Incoming Webhook" connector with URLs on webhook.office.com. Microsoft has retired those connectors. Use the Workflows app in Teams instead, as described below. It gives you a URL on a Power Automate host.

Prerequisites​

  • A Teams channel or chat where you're allowed to add workflows. Your Teams administrator may need to allow the Workflows app.
  • A Vince Platform workflow that produces the information you want to post. This guide uses an order export as the example.
  • Familiarity with the REST API and Transform components.

Step 1 — Create the webhook in Teams​

In Teams, create a workflow from the template Send webhook alerts to a channel (for a channel) or Send webhook alerts to a chat (for a chat). Depending on your Teams version, it can also appear as Post to a channel when a webhook request is received. Pick the team and channel, then finish the wizard. Teams then shows the webhook URL. Copy it.

Microsoft changes the names of these menus and templates from time to time. For the current steps, see Microsoft's guide Create incoming webhooks with Workflows for Microsoft Teams.

Treat the webhook URL as a password

Anyone who has the URL can post into your channel, with no login needed. The URL carries its own signature (the sig= part). Don't paste it into tickets, chat messages or screenshots. If it leaks, delete the workflow in Teams and create a new one; that gives you a new URL.

Step 2 — Build the message with Transform​

In this example, a REST API step (rest_api_1) has sent orders to another system, and its body holds the result of that export:

{
"orders": [
{ "orderNumber": "SO-50211", "status": "SENT", "message": "OK" },
{ "orderNumber": "SO-50212", "status": "FAILED", "message": "Unknown customer C-0042" },
{ "orderNumber": "SO-50213", "status": "FAILED", "message": "Warehouse WH9 is closed" }
]
}

Add a Transform step that builds an Adaptive Card listing the orders that failed:

(
$orders := $context.data.all.rest_api_1.body.orders;
$failed := $orders[status != "SENT"];
{
"failedCount": $count($failed),
"request": {
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "Order export: " & $string($count($failed)) & " of " & $string($count($orders)) & " orders failed",
"weight": "Bolder",
"size": "Medium",
"wrap": true
},
{
"type": "FactSet",
"facts": $failed.{ "title": orderNumber, "value": message }[]
}
]
}
}
]
}
}
)

The expression returns two things:

  • failedCount: used in Step 3 to skip the notification when there's nothing to report.
  • request: the message itself. Its outer wrapper ("type": "message" plus an attachments list containing the card) is the format the Teams webhook expects. Inside it:
    • a TextBlock, the heading
    • a FactSet, the list of order number and reason pairs

The [] after the facts mapping keeps facts a list even when only one order failed. Adaptive Cards require a list there.

With the example input, request comes out as:

{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{ "type": "TextBlock", "text": "Order export: 2 of 3 orders failed", "weight": "Bolder", "size": "Medium", "wrap": true },
{
"type": "FactSet",
"facts": [
{ "title": "SO-50212", "value": "Unknown customer C-0042" },
{ "title": "SO-50213", "value": "Warehouse WH9 is closed" }
]
}
]
}
}
]
}
tip

Design the card in Microsoft's Adaptive Cards Designer first. Paste the card JSON (the part under content) and see a live preview, then move it into your Transform and replace the fixed values with JSONata expressions.

Step 3 — Post to Teams​

Add a REST API step after the Transform:

SettingValue
Use connectionOff: the webhook URL carries its own signature
Endpoint URLThe webhook URL you copied in Step 1
HTTP Methodpost

Under Headers, select Add header:

KeyValue
content-typeapplication/json

Under Request body, use the Body path tab and set it to request. This sends only the message, not failedCount.

Under Advanced:

SettingValue
Version3
Skip if expressionbody.failedCount = 0, so nobody gets a message when all orders went through

When Teams accepts the message, the step returns statusCode 202 (Accepted). The message appears in the channel shortly after, posted by the Workflows app.

Troubleshooting​

SymptomLikely cause
statusCode 400The body isn't valid for the webhook, often because the type/attachments wrapper is missing or contentType is misspelled. Check the request in Execution Logs.
statusCode 401 or 404The URL is incomplete (the sig= part is missing) or the Teams workflow was deleted. Copy the URL again from Teams.
202, but nothing appears in the channelTeams accepted the request, but its own workflow failed. Open the workflow's run history in Teams or Power Automate to see why, for example a card that doesn't render.
The card shows without the listfacts wasn't a list. Check the [] after the mapping.

Variations​

  • Post to several channels: create one Teams workflow per channel, then add one REST API step per URL, or one REST API step with For each over a list of URLs built in the Transform. See REST API in Practice.
  • Notify about a single record: in a workflow started by an Event trigger, build the card from the record that changed. For example: "Order SO-50212 is ready to ship".
  • Email instead of, or as well as, Teams: see Email a Run Summary.

Further reading​