May 06, 2026

Telegram API Returning 400 Bad Request Errors

Diagnosing and resolving 400 HTTP status codes from the Telegram Bot API caused by authentication failures, malformed requests, or configuration issues in OpenClaw integrations.

πŸ” Symptoms

Telegram bot integrations fail with HTTP 400 status codes. The following manifestations are observed:

API Response Pattern

2024-01-15T10:23:45.123Z [ERROR] Telegram API Error { “ok”: false, “error_code”: 400, “description”: “Bad Request: chat not found” }

CLI Verification Commands

# Test Telegram bot connectivity
curl -X POST "https://api.telegram.org/bot${BOT_TOKEN}/getMe" \
  -H "Content-Type: application/json"

# Expected response for valid token:
{"ok":true,"result":{"id":123456789,"is_bot":true,"first_name":"TestBot","username":"testbot"}}

# Error response for invalid token:
{"ok":false,"error_code":401,"description":"Unauthorized"}

# Check pending webhook
curl -X POST "https://api.telegram.org/bot${BOT_TOKEN}/getWebhookInfo"

Common Error Messages

  • Bad Request: chat not found β€” Invalid or expired chat_id
  • Bad Request: message text is empty β€” Empty message payload
  • Bad Request: can't parse entities β€” Invalid Markdown/HTML formatting
  • Bad Request: wrong type of the web page element β€” Inline keyboard configuration error
  • Bad Request: query is too old β€” Callback query expiration (24+ hours)

Application Logs

# OpenClaw log output pattern
[ERROR] TelegramService: sendMessage failed
  status: 400
  response: {"ok":false,"error_code":400,"description":"Bad Request: chat not found"}
  chat_id: undefined
  bot_token: ****_****_****

🧠 Root Cause

HTTP 400 errors from the Telegram Bot API indicate malformed requests sent by the OpenClaw Telegram channel handler. The root causes fall into four categories:

1. Authentication Token Misconfiguration

The BOT_TOKEN environment variable contains an invalid, revoked, or malformed token. Telegram tokens follow the format:

${digits}:${alphanumeric_string}

Tokens can become invalid through:

  • Revocation via @BotFather β†’ /revoke
  • Copy-paste errors introducing whitespace or character substitution
  • Multiple bot accounts sharing misconfigured token values

2. Chat ID Resolution Failure

The Telegram API requires explicit chat_id for all operations. OpenClaw resolves chat IDs through:

  • Webhook payloads (inbound messages include message.chat.id)
  • Stored user metadata from /start command registration
  • Direct API calls using getUpdates method

When the chat ID is undefined, null, or negative, the API returns 400 Bad Request: chat not found.

3. Message Payload Encoding Issues

Telegram’s sendMessage method requires properly escaped HTML or Markdown entities:

EntityHTML ModeMarkdown Mode
Bold<strong>text
Italic<em>text
Code<code>text
Pre<pre>text

Unescaped characters (<, >, &) in raw payloads cause parse failures.

4. Webhook State Conflicts

If a webhook is active and getUpdates polling is attempted, Telegram returns 409 Conflict. If configuration cycles between modes rapidly, stale update_id sequences can produce 400 errors on replayed updates.

Failure Sequence Diagram

OpenClaw Request β†’ Telegram API
                          ↓
                  [400 Bad Request]
                          ↓
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              ↓                       ↓
        Parameter Error         Authentication Error
        (chat_id, text)          (token, permissions)

πŸ› οΈ Step-by-Step Fix

Phase 1: Token Validation

Step 1.1: Retrieve and verify current token

# Check environment variable (bash)
echo $TELEGRAM_BOT_TOKEN

# Check environment variable (PowerShell)
$env:TELEGRAM_BOT_TOKEN

# Verify token format
# Expected: 123456789:ABCdefGHIjklMNO12pqrSTUvwxYZ34

Step 1.2: Test token with getMe endpoint

TELEGRAM_BOT_TOKEN="your_token_here"

curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe" | jq .

If ok: false with error_code: 401, obtain a new token from @BotFather.

Step 1.3: Generate new token if required

  1. Open Telegram and message @BotFather
  2. Send /token
  3. Select bot or create new with /newbot
  4. Update environment: export TELEGRAM_BOT_TOKEN="new:token"

Phase 2: Chat ID Resolution

Step 2.1: Enable debug logging to capture chat_id

# openclaw.yaml configuration
channels:
  telegram:
    bot_token: "${TELEGRAM_BOT_TOKEN}"
    debug: true
    log_level: debug

Step 2.2: Clear webhook and use getUpdates

# Remove existing webhook
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/deleteWebhook"

# Poll for pending updates
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates"

Step 2.3: Store valid chat_id

# Extract chat ID from update payload
{
  "ok": true,
  "result": [
    {
      "update_id": 123456789,
      "message": {
        "chat": {
          "id": -987654321,  // <-- This is your group ID (negative)
          "type": "group"
        }
      }
    }
  ]
}

Phase 3: Message Payload Sanitization

Step 3.1: Escape HTML entities

# Before (invalid)
message_text: "<div>Hello & World</div>"

# After (valid HTML)
message_text: "&lt;div&gt;Hello &amp; World&lt;/div&gt;"

Step 3.2: Configure parse_mode in OpenClaw

# openclaw.yaml - explicit parse mode
channels:
  telegram:
    bot_token: "${TELEGRAM_BOT_TOKEN}"
    parse_mode: "HTML"  # Options: "HTML", "Markdown", null

Phase 4: Webhook Configuration

Step 4.1: Verify webhook URL

curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getWebhookInfo"

Step 4.2: Set webhook if using webhook mode

NGROK_URL="https://abc123.ngrok.io"

curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook" \
  -d "url=${NGROK_URL}/webhooks/telegram"

Step 4.3: Restart OpenClaw service

# Docker
docker-compose restart openclaw

# Systemd
sudo systemctl restart openclaw

# Direct process
pkill -f openclaw && nohup openclaw &amp; &amp;

πŸ§ͺ Verification

Test 1: Token Validity

curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe" | jq .

# Expected output:
{
  "ok": true,
  "result": {
    "id": 123456789,
    "is_bot": true,
    "first_name": "YourBotName",
    "username": "yourbotname"
  }
}

Test 2: Outbound Message Sending

CHAT_ID="your_chat_id"

curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
  -H "Content-Type: application/json" \
  -d '{
    "chat_id": "'"${CHAT_ID}"'",
    "text": "OpenClaw test message",
    "parse_mode": "HTML"
  }' | jq .

Expected output:

{
  "ok": true,
  "result": {
    "message_id": 123,
    "chat": { "id": CHAT_ID, "type": "private" },
    "date": 1705322345,
    "text": "OpenClaw test message"
  }
}

Test 3: OpenClaw Integration Test

# Send test message via OpenClaw CLI
openclaw channels telegram test --chat-id "${CHAT_ID}"

# Or programmatic test
curl -X POST "http://localhost:3000/api/channels/telegram/test" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ${OPENCLAW_API_KEY}"

Test 4: Webhook Reception

# Check webhook info
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getWebhookInfo" | jq .

# Expected: "url" field populated, "pending_update_count": 0

Verification Exit Criteria

  • getMe returns ok: true
  • sendMessage returns ok: true with message_id
  • Application logs show [INFO] TelegramService: message sent successfully
  • No HTTP 400 errors in application logs for 5+ consecutive operations

⚠️ Common Pitfalls

Environment-Specific Traps

Docker Container Networking

# Problem: Webhook URL points to internal Docker network
# e.g., webhook set to http://172.17.0.2/webhook/telegram

# Solution: Use public-facing tunnel
services:
  openclaw:
    environment:
      - TELEGRAM_WEBHOOK_URL=https://public.domain.com/webhooks/telegram
    tunnels:
      - name: telegram
        service: http://openclaw:3000

macOS Certificate Issues

# Problem: curl fails with SSL certificate error on macOS
# Error: "SSL certificate problem: unable to get local issuer certificate"

# Solution: Update CA certificates
brew install curl-openssl
# Or disable SSL verification (development only):
curl -k -X POST "https://api.telegram.org/..."

Windows Path Separators

# Problem: Newline handling in JSON on Windows
# Windows uses CRLF (\r\n) which can corrupt JSON payloads

# Solution: Ensure UTF-8 LF output
$json = $body | ConvertTo-Json -Compress
$body = $json -replace "`r`n", ""

Configuration Missteps

PitfallSymptomResolution
Double-quoting JSON valuesBad Request: can’t parse entitiesUse raw strings, escape quotes
Negative chat_id in private chatschat not foundPrivate chats use positive IDs
Rate limit exceededBad Request: flood controlImplement message queuing
Stale getUpdates cacheOut-of-order message deliveryClear updates after webhook set
Missing parse_mode declarationEntities not formattedExplicitly set parse_mode

Race Conditions

  • Webhook + getUpdates conflict: Telegram blocks concurrent usage. Always deleteWebhook before polling.
  • Token rotation: Old tokens may cache in load balancers. Propagate immediately.
  • Chat ID caching: User blocking/unblocking bot invalidates stored chat_id.

Security Considerations

# NEVER commit tokens to version control
# Add to .gitignore:
echo "TELEGRAM_BOT_TOKEN" >> .gitignore
git secrets --add-provider --喝了${HOME}/.git-secrets/telegram-hook.sh

Telegram Bot API Error Codes

CodeErrorCauseReference
400Bad RequestMalformed parametersCurrent issue
401UnauthorizedInvalid bot tokenToken validation
403ForbiddenBot blocked by userUser permissions
404Not FoundInvalid chat_id (non-existent)Chat ID resolution
409ConflictWebhook/getUpdates conflictWebhook configuration
429Too Many RequestsRate limit exceededRate limiting
500Internal Server ErrorTelegram server issueRetry with backoff

OpenClaw Historical Issues

  • GH-#452 β€” Telegram webhook signature validation fails on messages exceeding 4KB payload size
  • GH-#398 β€” Chat ID stored as string instead of integer causes silent failures
  • GH-#367 β€” Memory leak in long-polling mode causing getUpdates 400 errors after 24h
  • GH-#301 β€” HTML entity encoding double-escapes ampersands in sendMessage payloads
  • Slack 400 errors β€” Often indicate malformed Block Kit JSON (similar parsing issues)
  • Discord 40015 β€” Webhook fetch failed (analogous webhook configuration issue)

External Resources

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.