Skip to main content

Post from Telegram to WordPress — Automated CMS Publishing Bot

Publish blog posts, formatted HTML updates, and high-resolution photo attachments directly from a Telegram channel or chat to WordPress via the official /wp/v2/ REST API.

Post from Telegram to WordPress Architecture


Technical Architecture: Reverse-Engineering the Telegram-to-WordPress Bridge​

Publishing micro-blog updates or photo logs to a WordPress site from a mobile browser requires navigating a heavy admin dashboard. I built post-from-telegram-to-wordpress as a containerized Python daemon that listens to authorized Telegram channels, uploads binary media to /wp-json/wp/v2/media, and publishes structured posts to /wp-json/wp/v2/posts.

[ Telegram Channel / Chat ]
│
│ (Telegram Bot API / Event Listener)
▼
[ Python Listener Service ]
│
├── 1. Verify Chat ID Allowlist & Extract HTML/Markdown Entities
├── 2. Download Highest-Resolution Telegram Photo (photo[-1])
├── 3. Upload Binary Stream to WordPress (/wp-json/wp/v2/media)
│
▼ (WordPress Application Password Basic Auth)
[ WordPress REST API (/wp-json/wp/v2/posts) ]
│
▼
[ Live WordPress Post with Featured Image ]

1. WordPress Application Password Authentication​

Instead of relying on cookie sessions or main user passwords, the bridge authenticates using WordPress Application Passwords (native since WordPress 5.6), encoding username:app_password as an RFC 7617 Basic Authorization header over HTTPS:

import base64

def get_wp_auth_header(username: str, app_password: str) -> dict:
credentials = f"{username}:{app_password}"
token = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
return {"Authorization": f"Basic {token}"}

2. Allowlisted Telegram Event Listener​

Incoming updates are filtered against an explicit ALLOWED_CHAT_IDS set so only designated channel administrators or private chats can trigger WordPress publication:

from telegram import Update
from telegram.ext import ContextTypes

ALLOWED_CHAT_IDS = {-100123456789} # Target Telegram Channel / Admin ID

async def handle_incoming_post(update: Update, context: ContextTypes.DEFAULT_TYPE):
message = update.effective_message
if not message or message.chat.id not in ALLOWED_CHAT_IDS:
return

text_content = message.text_html or message.caption_html or ""
photo_attachments = message.photo

await process_and_publish(text_content, photo_attachments, context.bot)

3. Two-Stage Media & Post Publishing Pipeline​

When a Telegram message includes a photo, the daemon fetches the highest-resolution variant (message.photo[-1]), streams the binary buffer to /wp-json/wp/v2/media with a Content-Disposition filename header, captures the returned attachment id, and binds it as featured_media on the new post:

import requests

def upload_photo_to_wordpress(wp_site_url: str, auth_headers: dict, photo_bytes: bytes, filename: str) -> int:
media_url = f"{wp_site_url}/wp-json/wp/v2/media"
headers = {
**auth_headers,
"Content-Disposition": f'attachment; filename="{filename}"',
"Content-Type": "image/jpeg",
}

response = requests.post(media_url, headers=headers, data=photo_bytes, timeout=30)
response.raise_for_status()
return response.json()["id"]

def create_wordpress_post(
wp_site_url: str,
auth_headers: dict,
title: str,
html_content: str,
media_id: int | None = None,
) -> dict:
posts_url = f"{wp_site_url}/wp-json/wp/v2/posts"
payload = {
"title": title,
"content": html_content,
"status": "publish", # Configurable: 'publish' or 'draft'
}

if media_id is not None:
payload["featured_media"] = media_id

response = requests.post(posts_url, headers=auth_headers, json=payload, timeout=30)
response.raise_for_status()
return response.json()

Configuration & Environment Reference​

Environment VariableExample ValuePurpose
TELEGRAM_BOT_TOKEN123456789:AAH...Bot token issued by @BotFather
ALLOWED_CHAT_ID-100123456789Authorized Telegram channel or user chat ID
WP_SITE_URLhttps://example.comBase URL of the target WordPress installation
WP_USEReditor_botWordPress username with upload_files and publish_posts roles
WP_APP_PASSWORDxxxx xxxx xxxx xxxxRevokable WordPress Application Password

Security & Deployment Architecture​

  • Least-Privilege WordPress Credentials: Uses revokable Application Passwords scoped to a dedicated WordPress Author/Editor account rather than a Super Admin account.
  • Strict Origin Filtering: Drops updates from any Telegram chat or user ID not explicitly listed in ALLOWED_CHAT_IDS.
  • Stateless Container Execution: Runs inside a python:3.11-slim Docker container without requiring local database volumes or persistent media folders.

Get notified about updates to this tool

No spam. Just high-quality updates about new features and guides.