Webhook to Telegram: Transform & Forward Any Payload to a Chat
Send a webhook to Telegram. Step-by-step guide to forward an incoming webhook into a Telegram chat, transforming the raw payload into the Bot API's sendMessage format in flight.

You have a service that fires webhooks — a CI pipeline, a contact form, an uptime monitor, a payment provider, an alerting tool — and you want each event to show up in a Telegram chat. The problem: Telegram doesn't accept arbitrary payloads. Its Bot API 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 the Telegram Bot API 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 { chat_id, text } ──▶ Telegram chat
(raw webhook) (public URL) (serverless function) (message appears)
To post a message, you call the Telegram Bot API's sendMessage method by POSTing JSON to:
https://api.telegram.org/bot<TOKEN>/sendMessage
with a body like:
{ "chat_id": "123456789", "text": "your message" }
A generic incoming webhook isn't shaped like that, so we add a small transformation function that builds the { chat_id, text } body from the raw payload before delivery. That one feature is what turns Webhook Relay into a universal "anything → Telegram" bridge.
Step 1: Create a bot and get its token
Open Telegram and start a chat with @BotFather. Send /newbot, give the bot a name and a username, and BotFather replies with an HTTP API token that looks like:
123456789:AAExampleTokenStringFromBotFather
Keep this token secret — anyone who has it can send messages as your bot.
Step 2: Find your chat_id
Telegram won't let your bot message you until you've talked to it first, so send any message (like /start) to your new bot. Then fetch recent updates by opening this URL in a browser, replacing <TOKEN> with yours:
https://api.telegram.org/bot<TOKEN>/getUpdates
In the JSON response, find result[].message.chat.id — that number is your chat_id. For a group chat, add the bot to the group, post a message there, and read the (usually negative) group chat.id from the same getUpdates output.
Step 3: Create a Webhook Relay public endpoint
Open the new public destination page and paste the Bot API URL as the destination:
https://api.telegram.org/bot<TOKEN>/sendMessage
Webhook Relay gives you back an input URL — a public endpoint that you'll point your source service at. Everything sent to it gets forwarded toward Telegram.
Step 4: Add a transformation function
Right now, anything you send to the input URL would be forwarded to sendMessage as-is — and Telegram would ignore it, because it isn't in { "chat_id": "...", "text": "..." } shape. 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")
-- the chat the message should land in
local chat_id = "123456789"
local payload = {
chat_id = chat_id,
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 as the message text, pairs it with your chat_id, sets the right headers and method, and lets Webhook Relay deliver it. Telegram now accepts the call and posts the message to your chat.
Step 5: Point your source at the URL and test
Configure your source service (the CI tool, form, monitor, 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 Telegram chat. 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 formatted message. If your source sends JSON, parse it and construct a richer text string. Telegram supports Markdown and HTML formatting, so you can add bold headers and bullet points:
local json = require("json")
local event = json.decode(r.RequestBody)
local text = "*Deployment " .. event.status .. "*\n" .. event.message
local payload = {
chat_id = "123456789",
text = text,
parse_mode = "Markdown",
}
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 chats. Add more destinations so one incoming webhook lands in several Telegram chats — or in Telegram and Discord — at the same time. See forwarding to multiple destinations.
The pattern here is identical to the one in Send a webhook to Discord — only the destination URL and the body shape change. For a complete real-world walkthrough, see TradingView alerts, where a plain alert message is transformed into a chat notification.
FAQ
What does Telegram need to receive? A POST with a JSON body of { "chat_id": "...", "text": "your message" } to https://api.telegram.org/bot<TOKEN>/sendMessage. The transform function produces exactly that.
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 → Telegram" 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.
