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_idBad Request: message text is emptyβ Empty message payloadBad Request: can't parse entitiesβ Invalid Markdown/HTML formattingBad Request: wrong type of the web page elementβ Inline keyboard configuration errorBad 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
/startcommand registration - Direct API calls using
getUpdatesmethod
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:
| Entity | HTML Mode | Markdown Mode |
|---|---|---|
| Bold | <strong> | text |
| Italic | <em> | text |
| Code | <code> | |
| 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
- Open Telegram and message
@BotFather - Send
/token - Select bot or create new with
/newbot - 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: "<div>Hello & World</div>"
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 & &
π§ͺ 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
getMereturnsok: truesendMessagereturnsok: truewithmessage_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
| Pitfall | Symptom | Resolution |
|---|---|---|
| Double-quoting JSON values | Bad Request: can’t parse entities | Use raw strings, escape quotes |
| Negative chat_id in private chats | chat not found | Private chats use positive IDs |
| Rate limit exceeded | Bad Request: flood control | Implement message queuing |
| Stale getUpdates cache | Out-of-order message delivery | Clear updates after webhook set |
| Missing parse_mode declaration | Entities not formatted | Explicitly set parse_mode |
Race Conditions
- Webhook + getUpdates conflict: Telegram blocks concurrent usage. Always
deleteWebhookbefore 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
π Related Errors
Telegram Bot API Error Codes
| Code | Error | Cause | Reference |
|---|---|---|---|
| 400 | Bad Request | Malformed parameters | Current issue |
| 401 | Unauthorized | Invalid bot token | Token validation |
| 403 | Forbidden | Bot blocked by user | User permissions |
| 404 | Not Found | Invalid chat_id (non-existent) | Chat ID resolution |
| 409 | Conflict | Webhook/getUpdates conflict | Webhook configuration |
| 429 | Too Many Requests | Rate limit exceeded | Rate limiting |
| 500 | Internal Server Error | Telegram server issue | Retry 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
Cross-Channel Related Errors
- Slack 400 errors β Often indicate malformed Block Kit JSON (similar parsing issues)
- Discord 40015 β Webhook fetch failed (analogous webhook configuration issue)
External Resources
- Telegram API Error Codes β Official error documentation
- Telegram Bot API Reference β Method specifications
- Telegram Bot FAQ β Common integration questions