Send a Webhook to Microsoft Teams: Transform & Forward Any Payload
Send any incoming webhook to Microsoft Teams. Forward an event through Webhook Relay, transform the raw payload into the Teams message JSON in flight, and post it to a Teams Incoming Webhook or Power Automate Workflow URL.
You have a service that fires webhooks — a CI pipeline, an uptime monitor, a form, a payment provider, an alerting tool — and you want each event to land in a Microsoft Teams channel. The problem: Teams doesn't accept arbitrary payloads. It expects a specific JSON body, and the webhook your source sends almost never matches that shape.
Webhook Relay sits in the middle. It receives the incoming webhook at a stable public URL, transforms the payload into the format Teams expects, and delivers it — no glue server, no Lambda, no maintenance.
How it works
The flow is a single hop with a transform in the middle:
Your service ──▶ Webhook Relay ──▶ transform to Teams JSON ──▶ Teams channel
(raw webhook) (public URL) (serverless function) (message appears)
The simplest message Teams accepts is a JSON body with a text field POSTed to a Teams Incoming Webhook URL:
{ "text": "your message" }
A generic incoming webhook isn't shaped like that, so we add a small transformation function that wraps the raw body into Teams' format before delivery. That one feature is what turns Webhook Relay into a universal "anything → Teams" bridge.
A note on connectors vs. Workflows
Historically, Teams Incoming Webhooks came from Office 365 connectors and accepted the legacy MessageCard format. Microsoft is now migrating away from connectors toward Power Automate Workflows, where the modern format is an Adaptive Card. New integrations should create a Workflow-based Incoming Webhook.
Good news: the Webhook Relay side doesn't change. Whichever URL you're given, your job is the same — produce the JSON that endpoint expects and POST it. The { "text": "..." } shape below is the quickest to get working; we'll show the richer Adaptive Card variant at the end.
Step 1: Create the Teams webhook URL
In Teams, the current path is + New (or the channel's ••• menu) → Workflows → "Post to a channel when a webhook request is received" (a Power Automate template). Select the team and channel, finish the flow, and copy the generated HTTP POST URL. It's a long Power Automate / Logic Apps URL.
If your tenant still uses the legacy route, you'd add an Incoming Webhook connector to the channel instead and copy that URL. Either URL works as a destination below.
Keep this URL handy — it's where Webhook Relay will deliver.
Step 2: Create a Webhook Relay public endpoint
Open the new public destination page and paste the Teams webhook URL as the destination. Webhook Relay gives you back an input URL — a public endpoint you'll point your source service at.
That input URL is a stable address you can hand to any service. Everything sent to it gets forwarded toward Teams.
Step 3: Add a transformation function
Right now, anything you send to the input URL would be forwarded to Teams as-is — and Teams would reject it, because it isn't in the shape it expects. Add a transformation to fix that.
Click Transform on the input, open the Functions page, create a function "from scratch", and paste this Lua:
local json = require("json")
local payload = {
text = r.RequestBody,
}
local body, err = json.encode(payload)
if err then error(err) end
r:SetRequestHeader("content-type", "application/json")
r:SetRequestMethod("POST")
r:SetRequestBody(body)
This takes the raw incoming body, wraps it as { "text": "..." }, sets the right headers and method, and lets Webhook Relay deliver it. Teams now accepts the message and posts it to your channel.
Step 4: Point your source at the URL and test
Configure your source service (the CI tool, monitor, form, payment provider — whatever you're integrating) to send its webhook to the Webhook Relay input URL.
To confirm everything works before wiring up the real service, fire a quick test with curl:
curl -X POST https://my.webhookrelay.com/v1/webhooks/your-input-id \
-d 'Deployment finished: build #482 passed on main'
Within a second or two, Deployment finished: build #482 passed on main appears in your Teams channel. If nothing shows up, open the request log in your dashboard to see exactly what was received and delivered.
Going further
Once the basic bridge works, the transform function is where the real power is.
Build a richer Adaptive Card. Workflow-based webhooks expect an Adaptive Card wrapped in an attachments array. If your source sends JSON, parse it and construct the card so you get titled, formatted messages instead of a flat string:
local json = require("json")
local event = json.decode(r.RequestBody)
local payload = {
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", size = "Large", weight = "Bolder",
text = "Deployment " .. event.status },
{ type = "TextBlock", wrap = true, text = event.message },
},
},
},
},
}
local body, err = json.encode(payload)
if err then error(err) end
r:SetRequestHeader("content-type", "application/json")
r:SetRequestMethod("POST")
r:SetRequestBody(body)
Filter out noise. Not every event deserves a ping. Use forwarding rules to only deliver events that match a condition, or drop them in the function with an early return.
Fan out to multiple channels. Add more destinations so one incoming webhook lands in several Teams channels — or in Teams and Slack and Discord — at the same time. See forwarding to multiple destinations.
FAQ
What does Teams need to receive? A POST with a JSON body. The minimum is { "text": "your message" }; the modern Workflow path expects an Adaptive Card inside an attachments array. The transform function produces exactly the shape your URL expects.
Do I need to run a server? No. Webhook Relay receives, transforms, and delivers everything in the cloud. There's nothing to host or keep online.
How do I see the raw payload my source sends? Send it to a free Webhook Bin first to inspect the exact body and headers, then write your transform around that structure.
Get started
Create a free Webhook Relay account and set up your first "anything → Teams" bridge in a few minutes. Not sure what your source sends? Inspect the payload in a Webhook Bin first, then write the transform to match.
