# Get downtime alerts in Telegram

> Telegram's Bot API wants its own request shape, not YoPingMe's webhook payload. A small pasteable Cloudflare Worker bridges the two.

Published: 2026-08-08 | Canonical: https://yoping.me/integrations/telegram

## Can I point a YoPingMe webhook straight at Telegram?

No. Telegram's Bot API expects a `sendMessage` call with a `chat_id` and
`text` of its own shape, and it ignores anything else you POST at it.
YoPingMe's webhook channel sends one fixed JSON payload (documented in
[the Discord recipe](/integrations/discord#the-webhook-payload), the same
contract for every receiver). The bridge is the same pattern as the
Discord relay: a Worker of about 25 lines that reshapes the alert into a
Telegram message and forwards it.

You need: a Telegram account, a free Cloudflare account, and a YoPingMe
monitor.

## Step 1: create the bot and get its token

Message `@BotFather` in Telegram, send `/newbot`, and follow the two
prompts (a display name, then a username ending in `bot`). BotFather
replies with the bot's API token. Treat the token as a secret - anyone
who has it can send messages as your bot.

## Step 2: get your chat id

The bot needs somewhere to send alerts: a private chat with you, or a
group.

- **Private chat:** open a chat with your new bot and send it any
  message (bots cannot message you first).
- **Group:** add the bot to the group, then send any message in it.

Then read the chat id from the bot's update queue:

```bash
curl -s "https://api.telegram.org/bot<TOKEN>/getUpdates"
```

In the JSON reply, `result[0].message.chat.id` is the value you want. A
private chat id is a positive number; a group id is negative, minus sign
included.

## Step 3: create the relay Worker

In the Cloudflare dashboard, create a new Worker (Workers and Pages,
then Create). Replace its code with:

```js
export default {
  async fetch(request, env) {
    if (request.method !== 'POST') {
      return new Response('expected POST', { status: 405 });
    }
    const alert = JSON.parse(await request.text());
    const icon = { down: '🔴', up: '🟢', still_down: '🟡' }[alert.event] ?? '🔵';
    const label = { down: 'Down', up: 'Recovered', still_down: 'Still down' }[alert.event] ?? alert.event;

    const lines = [
      `${icon} ${label}: ${alert.monitor}`,
      alert.target,
      alert.cause ?? '',
    ].filter(Boolean);

    const tg = await fetch(`https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ chat_id: env.TELEGRAM_CHAT_ID, text: lines.join('\n') }),
    });

    // A non-2xx answer makes YoPingMe retry, so a Telegram hiccup is
    // covered by the same retry schedule as everything else.
    return new Response(null, { status: tg.ok ? 200 : 502 });
  },
};
```

The message is deliberately plain text, no `parse_mode`: Telegram's
Markdown and HTML modes reject messages when a monitor name or cause
string happens to contain a character they treat as markup, and a
dropped alert is a bad trade for bold text.

This recipe uses Cloudflare Workers for the same reason the Discord one
does: the free tier (100,000 requests a day, checked 2026-08-07) will
not notice your alert volume.

## Step 4: set the Worker's secrets

In the Worker's settings, add both as secrets (not plaintext variables):

- `TELEGRAM_BOT_TOKEN`: the token from step 1.
- `TELEGRAM_CHAT_ID`: the id from step 2.

With the wrangler CLI instead: `wrangler secret put TELEGRAM_BOT_TOKEN`
and `wrangler secret put TELEGRAM_CHAT_ID`.

## Step 5: add the channel in YoPingMe

In the YoPingMe dashboard, add an alert channel of type webhook. The URL
is your Worker's URL. Attach the channel to your monitors.

As with the Discord relay, the Worker's URL is the credential: there is
no shared secret between YoPingMe and the relay, so anyone who has the
URL can make your bot post. Keep it private, and rotate it by creating a
new Worker (new name, new URL) if it leaks - the
[Discord recipe](/integrations/discord#keep-the-worker-url-private)
spells out the steps.

## Step 6: send a test alert

Simulate what YoPingMe sends:

```bash
WORKER_URL='https://your-relay.example.workers.dev'
BODY='{"event":"down","idempotency_key":"test-1","incident_id":"inc-test","monitor":"my site","target":"https://example.com","cause":"HTTP 500","sequence_at":"2026-08-08T12:00:00Z"}'
curl -sS -X POST "$WORKER_URL" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: test-1' \
  --data "$BODY"
```

A "🔴 Down: my site" message should arrive in the chat.

## Delivery guarantees

The full webhook contract - fields, headers, signature - is documented in
[the Discord recipe](/integrations/discord#the-webhook-payload). The
short version: 10 second timeout, up to 5 attempts over about 10 minutes
of doubling backoff, at-least-once delivery, and duplicates carry the
same `idempotency_key`. The relay forwards duplicates as-is; Telegram
showing the same alert twice during a retry storm is the honest signal
that delivery was rough, not a bug worth engineering away.
