PowerShell Teams Webhook: Post Messages to Teams Channel

When an automated script detects a critical issue — a failed service, a backup error, a disk filling up — your on-call team needs to know immediately. PowerShell can send Teams messages to any Microsoft Teams channel using Incoming Webhooks, turning your scripts into proactive alerting systems that reach your team where they already work. This post covers basic messages, Adaptive Card formatting, and a reusable notification function.
Quick Answer / TL;DR
Create an Incoming Webhook connector in Teams, then post JSON to it with Invoke-RestMethod -Uri $webhookUrl -Method Post -ContentType 'application/json' -Body $json. The payload format uses either legacy MessageCard or the newer Adaptive Card schema.
Create Teams Incoming Webhook Connector
Connectors are configured per channel in Teams. Navigate to the channel, click the three-dot menu, select Connectors, find “Incoming Webhook,” and configure it with a name and optional icon. Copy the generated URL — this is your endpoint for all POST requests. Note: as of late 2024, Microsoft is migrating away from Office 365 Connectors toward Power Automate workflows for new webhook integrations.
# Store webhook URL securely — never hardcode in scripts
$webhookUrl = $env:TEAMS_WEBHOOK_URL
# Quick connectivity test
$testPayload = @{
'@type' = 'MessageCard'
'@context' = 'http://schema.org/extensions'
text = 'PowerShell webhook test message'
} | ConvertTo-Json -Depth 5
$response = Invoke-RestMethod -Uri $webhookUrl -Method Post `
-ContentType 'application/json' -Body $testPayload
Write-Host "Response: $response" # Teams returns "1" on success
Send a Basic Message Card
The MessageCard schema is the original Teams webhook format. It supports a title, text, and optional sections. While Microsoft is phasing this out in favor of Adaptive Cards, it remains widely deployed and is simpler for basic notifications.
$payload = @{
'@type' = 'MessageCard'
'@context' = 'http://schema.org/extensions'
summary = 'Script Notification'
title = 'Backup Job Complete'
text = "Server **$env:COMPUTERNAME** completed backup at $(Get-Date -Format 'HH:mm:ss'). Files copied: 15,432."
} | ConvertTo-Json -Depth 5
Invoke-RestMethod -Uri $env:TEAMS_WEBHOOK_URL -Method Post `
-ContentType 'application/json' -Body $payload | Out-Null
Adaptive Card Format
Adaptive Cards are the modern Teams message format with richer layout options including columns, image sets, and action buttons. Post an Adaptive Card inside an attachments wrapper when using webhook endpoints.
$adaptiveCard = @{
type = 'message'
attachments = @(
@{
contentType = 'application/vnd.microsoft.card.adaptive'
contentUrl = $null
content = @{
'$schema' = 'http://adaptivecards.io/schemas/adaptive-card.json'
type = 'AdaptiveCard'
version = '1.4'
body = @(
@{ type = 'TextBlock'; size = 'Large'; weight = 'Bolder'; text = 'Deployment Report' },
@{ type = 'TextBlock'; text = "Server: $env:COMPUTERNAME"; wrap = $true },
@{ type = 'TextBlock'; text = "Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm')"; wrap = $true }
)
}
}
)
} | ConvertTo-Json -Depth 15
Invoke-RestMethod -Uri $env:TEAMS_WEBHOOK_URL -Method Post `
-ContentType 'application/json' -Body $adaptiveCard | Out-Null
Color-Coded Status Messages
MessageCard supports a themeColor property that adds a colored left border to the card. Use hex color codes: green for success, red for failure, orange for warnings. This provides instant visual status recognition in a busy Teams channel.
function Send-TeamsStatusMessage {
param(
[string]$Title,
[string]$Message,
[ValidateSet('Success','Warning','Error')]
[string]$Status = 'Success'
)
$colors = @{ Success = '00B050'; Warning = 'FFC000'; Error = 'FF0000' }
$payload = @{
'@type' = 'MessageCard'
'@context' = 'http://schema.org/extensions'
themeColor = $colors[$Status]
summary = $Title
title = $Title
text = $Message
sections = @(@{
activityText = "Reported by: $env:COMPUTERNAME"
activitySubtext = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
})
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Uri $env:TEAMS_WEBHOOK_URL -Method Post `
-ContentType 'application/json' -Body $payload | Out-Null
}
Send-TeamsStatusMessage -Title 'Database Backup Failed' -Message 'SQL Server returned error 1205' -Status 'Error'
Include Action Buttons
Add potentialAction entries to a MessageCard to include clickable buttons in the Teams message. Use OpenUri actions to link to dashboards, runbooks, or ticket systems.
$payload = @{
'@type' = 'MessageCard'
'@context' = 'http://schema.org/extensions'
themeColor = 'FF0000'
title = 'Disk Space Critical'
text = "Drive C: on $env:COMPUTERNAME is at 95% capacity."
potentialAction = @(
@{
'@type' = 'OpenUri'
name = 'Open Dashboard'
targets = @(@{ os = 'default'; uri = 'https://monitor.contoso.com' })
},
@{
'@type' = 'OpenUri'
name = 'View Runbook'
targets = @(@{ os = 'default'; uri = 'https://wiki.contoso.com/disk-cleanup' })
}
)
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Uri $env:TEAMS_WEBHOOK_URL -Method Post `
-ContentType 'application/json' -Body $payload | Out-Null
Reusable Send-TeamsMessage Function
A production-ready function with error handling, configurable webhook URL, and support for both text and pre-built card payloads. Import this from a shared module in all your automation scripts.
function Send-TeamsMessage {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$WebhookUrl,
[string]$Title,
[Parameter(Mandatory)]
[string]$Message,
[string]$Color = '0078D4',
[hashtable[]]$Actions
)
$card = @{
'@type' = 'MessageCard'
'@context' = 'http://schema.org/extensions'
themeColor = $Color
summary = $Title
title = $Title
text = $Message
}
if ($Actions) { $card.potentialAction = $Actions }
try {
$body = $card | ConvertTo-Json -Depth 10 -Compress
Invoke-RestMethod -Uri $WebhookUrl -Method Post `
-ContentType 'application/json' -Body $body -ErrorAction Stop | Out-Null
Write-Verbose 'Teams message sent successfully'
} catch {
Write-Warning "Teams notification failed: $($_.Exception.Message)"
}
}
Common Errors and Fixes
- Office 365 Connectors being deprecated — transition to Power Automate flow. Microsoft announced that Office 365 Connectors (the webhook mechanism used here) will be retired. New integrations should use Power Automate HTTP trigger flows, which accept the same JSON payloads via a POST to a flow URL. Existing connectors continue to work through the transition period.
- AdaptiveCard format differs from MessageCard format. Adaptive Cards require wrapping in an
attachmentsarray withcontentType: 'application/vnd.microsoft.card.adaptive'. MessageCards go directly as the POST body with@type: 'MessageCard'. Mixing up the two formats results in the message appearing blank or failing silently.
Related Cmdlets / See Also
Wrapping Up
Teams webhook notifications bridge your PowerShell automation with your team’s communication hub. Use MessageCards for quick alerts, Adaptive Cards for rich formatting, and color-coded borders for instant status recognition. Store webhook URLs in environment variables, handle errors gracefully so a Teams outage does not break your script, and monitor the connector deprecation timeline to plan migration to Power Automate.


