How to Build a Telegram Video Bot with n8n and a Video API

Mar 10, 2026 By smrht@icloud.com

How to Build a Telegram Video Bot with n8n and a Video API

A Telegram bot that generates videos on demand takes about 45 minutes to build with n8n and a JSON Video API. Users send a message, the bot creates a video, and sends it back -- all automated. No server code, no deployment headaches.

This guide covers the entire build from BotFather setup to a production bot handling hundreds of requests per day. I'll include every node configuration, the complete video JSON, and the error handling you need so the bot doesn't silently break at 2 AM.

Why Telegram for Video Delivery

Telegram supports video files up to 2GB. It has native inline video playback. It handles 15.6 billion messages per day and the bot API is free with no rate limits for reasonable usage (around 30 messages per second per bot).

Compare that to building a web app: you'd need a frontend, a backend, file hosting, user authentication, and a deployment pipeline. With Telegram, the app is the chat. Users already have it installed. They send a message, they get a video. Done.

Common use cases for Telegram video bots:

  • Demo video generators -- prospects request a personalized product demo
  • Daily content bots -- automated horoscope videos, news digests, motivational clips
  • E-commerce -- customers get video previews of products
  • Internal tools -- marketing teams request social video assets via chat
  • Education -- students receive video explanations on demand

Step 1: Create Your Telegram Bot

Open Telegram and search for @BotFather. Send /newbot and follow the prompts.

You: /newbot
BotFather: Alright, a new bot. How are we going to call it?
You: Video Generator Bot
BotFather: Good. Now let's choose a username.
You: video_gen_demo_bot
BotFather: Done! Your new bot is created.
         Token: 7123456789:AAHxR4k9mBvQ3nPpLwXzR7s2kJdFg5hY1cM

Save that token. You'll need it in n8n.

While you're in BotFather, set up the bot's commands so users know what's available:

/setcommands
video_gen_demo_bot
demo - Generate a demo video
product - Create product showcase
text - Text-to-video
help - Show available commands

Step 2: Set Up n8n Credentials

In your n8n instance, go to Credentials and create a new Telegram API credential:

  • Credential Type: Telegram API
  • Access Token: 7123456789:AAHxR4k9mBvQ3nPpLwXzR7s2kJdFg5hY1cM

If you don't have n8n running yet, the n8n Setup Guide walks through installation in under 10 minutes.

Next, create an HTTP Header Auth credential for the video API:

  • Credential Type: Header Auth
  • Header Name: Authorization
  • Header Value: Bearer YOUR_API_KEY

Get your API key from the JSON to Video dashboard.

Step 3: Build the Core Workflow

The complete workflow has 8 nodes. Here's the flow:

Telegram Trigger
    |
    v
Switch (Command Router)
    |
    +---> /demo ---> Code (Build Demo JSON)
    |                    |
    +---> /product --> Code (Build Product JSON)
    |                    |
    +---> /text -----> Code (Build Text JSON)
    |                    |
    +---> default ---> Telegram (Send Help)
    |
    v
HTTP Request (Submit Render)
    |
    v
Wait (15 seconds)
    |
    v
HTTP Request (Poll Status)
    |
    v
IF (Render Complete?)
    |
    +---> Yes ---> Telegram (Send Video)
    +---> No ----> Wait (Loop Back to Poll)

Node 1: Telegram Trigger

Add a Telegram Trigger node with these settings:

  • Credential: Your Telegram API credential
  • Updates: Message
  • Additional Fields: None needed for now

This node fires every time someone sends your bot a message.

Node 2: Switch (Command Router)

Add a Switch node to route based on the command:

Mode: Rules
Rules:
  1. Value: {{$json.message.text}}
     Operation: Starts With
     Value 2: /demo
     Output: 0

  2. Value: {{$json.message.text}}
     Operation: Starts With
     Value 2: /product
     Output: 1

  3. Value: {{$json.message.text}}
     Operation: Starts With
     Value 2: /text
     Output: 2

Fallback Output: 3

Node 3: Code Nodes (Build Video JSON)

Each command branch has a Code node that constructs the video JSON. Here's the demo video builder:

const message = $input.first().json.message;
const chatId = message.chat.id;
const userName = message.from.first_name || 'there';

// Extract custom text after the command, if any
const commandText = message.text.replace(/^\/demo\s*/, '').trim();
const headline = commandText || `Demo for ${userName}`;

const videoJson = {
  resolution: "1080x1920",
  fps: 30,
  quality: "high",
  scenes: [
    {
      duration: 4,
      background: { color: "#1a1a2e" },
      elements: [
        {
          type: "text",
          text: headline,
          style: {
            fontSize: 56,
            fontWeight: "bold",
            color: "#FFFFFF",
            textAlign: "center",
            maxWidth: 900
          },
          position: { x: "50%", y: "35%" },
          animation: { type: "fadeInUp", duration: 0.8 }
        },
        {
          type: "text",
          text: "Powered by SamAutomation",
          style: {
            fontSize: 28,
            color: "#8888aa"
          },
          position: { x: "50%", y: "55%" },
          animation: { type: "fadeIn", duration: 0.6, delay: 0.5 }
        }
      ]
    },
    {
      duration: 5,
      background: { color: "#16213e" },
      elements: [
        {
          type: "text",
          text: "Your product showcase goes here",
          style: {
            fontSize: 42,
            color: "#e0e0e0",
            textAlign: "center",
            maxWidth: 800
          },
          position: { x: "50%", y: "30%" }
        },
        {
          type: "shape",
          shape: "rectangle",
          style: {
            backgroundColor: "#0f3460",
            borderRadius: 16,
            width: 800,
            height: 400
          },
          position: { x: "50%", y: "62%" }
        },
        {
          type: "text",
          text: "Feature highlights\nPricing details\nCall to action",
          style: {
            fontSize: 32,
            color: "#FFFFFF",
            lineHeight: 2,
            textAlign: "center"
          },
          position: { x: "50%", y: "62%" },
          animation: { type: "fadeIn", duration: 0.5, delay: 0.3 }
        }
      ],
      transition: { type: "slideLeft", duration: 0.5 }
    },
    {
      duration: 3,
      background: { color: "#1a1a2e" },
      elements: [
        {
          type: "text",
          text: "Get Started Today",
          style: {
            fontSize: 52,
            fontWeight: "bold",
            color: "#e94560"
          },
          position: { x: "50%", y: "40%" },
          animation: { type: "bounceIn", duration: 0.6 }
        },
        {
          type: "text",
          text: "json2video.com",
          style: {
            fontSize: 30,
            color: "#8888aa"
          },
          position: { x: "50%", y: "60%" },
          animation: { type: "fadeIn", duration: 0.5, delay: 0.3 }
        }
      ],
      transition: { type: "fade", duration: 0.4 }
    }
  ],
  audio: {
    src: "https://assets.json2video.com/audio/ambient-tech.mp3",
    volume: 0.25,
    fadeOut: 2
  }
};

return [{ json: { videoJson, chatId } }];

The text-to-video builder is simpler -- it takes the user's message and turns it into a single-scene video with animated text:

const message = $input.first().json.message;
const chatId = message.chat.id;
const userText = message.text.replace(/^\/text\s*/, '').trim();

if (!userText) {
  return [{
    json: {
      chatId,
      error: true,
      errorMessage: "Send /text followed by your message. Example: /text Hello World"
    }
  }];
}

const videoJson = {
  resolution: "1080x1080",
  fps: 30,
  scenes: [
    {
      duration: 6,
      background: { color: "#000000" },
      elements: [
        {
          type: "text",
          text: userText,
          style: {
            fontSize: 48,
            color: "#FFFFFF",
            textAlign: "center",
            maxWidth: 900,
            lineHeight: 1.6
          },
          position: { x: "50%", y: "50%" },
          animation: { type: "typewriter", duration: 3 }
        }
      ]
    }
  ]
};

return [{ json: { videoJson, chatId } }];

Node 4: HTTP Request (Submit Render)

Configure the HTTP Request node:

Method: POST
URL: https://api.json2video.com/v2/renders
Authentication: Header Auth (your video API credential)
Body Type: JSON
Body: {{ $json.videoJson }}

Save the render ID from the response -- you'll need it for polling.

Node 5-6: Wait and Poll Loop

Add a Wait node set to 15 seconds. Then an HTTP Request node to check render status:

Method: GET
URL: https://api.json2video.com/v2/renders/{{ $json.id }}
Authentication: Header Auth

Node 7: IF Node (Check Completion)

Condition: {{$json.status}} equals "completed"
True: Continue to Send Video
False: Loop back to Wait node

Add a second condition for failure:

Condition: {{$json.status}} equals "failed"
True: Send error message to user

Node 8: Telegram Send Video

When the render is complete, send the video file to the user:

Operation: Send Video
Chat ID: {{ $('Code').first().json.chatId }}
Video: {{ $json.output_url }}
Caption: "Here's your video! Generated in {{ $json.render_time }}s"

Step 4: Add User Feedback and Error Messages

A bot that silently fails is worse than no bot at all. Add these feedback messages:

Acknowledgment (immediately after trigger): Before starting the render, send a quick message so the user knows the bot is working:

// Add a Telegram Send Message node right after the trigger
return [{
  json: {
    chatId: $input.first().json.message.chat.id,
    text: "Generating your video... This takes about 30-60 seconds."
  }
}];

Error handling: If the render fails, tell the user:

const chatId = $('Code').first().json.chatId;
const error = $input.first().json;

let errorMessage = "Sorry, the video generation failed. ";

if (error.status === 429) {
  errorMessage += "Too many requests. Please try again in a minute.";
} else if (error.message?.includes('resolution')) {
  errorMessage += "There was a problem with the video settings.";
} else {
  errorMessage += "Please try again or use /help for options.";
}

return [{ json: { chatId, text: errorMessage } }];

Step 5: Advanced Features

Inline Keyboards for User Choices

Instead of raw text commands, give users buttons:

const chatId = $input.first().json.message.chat.id;

return [{
  json: {
    chatId,
    text: "What kind of video do you want?",
    replyMarkup: {
      inline_keyboard: [
        [
          { text: "Product Demo", callback_data: "type_demo" },
          { text: "Text Video", callback_data: "type_text" }
        ],
        [
          { text: "Social Post", callback_data: "type_social" },
          { text: "Horoscope", callback_data: "type_horoscope" }
        ]
      ]
    }
  }
}];

The horoscope option connects to a daily horoscope video pipeline -- a fun use case that demonstrates how template-based generation works.

User Preferences Storage

Store user preferences so returning users get a personalized experience. Use n8n's built-in SQLite or connect to a database:

const userId = $input.first().json.message.from.id;
const command = $input.first().json.message.text;

// Parse preference from command like "/style dark"
const style = command.replace('/style ', '').trim();

// Save to workflow static data (persists between executions)
const staticData = $getWorkflowStaticData('global');
if (!staticData.userPrefs) staticData.userPrefs = {};

staticData.userPrefs[userId] = {
  ...staticData.userPrefs[userId],
  style: style,
  lastUsed: new Date().toISOString()
};

return [{ json: { saved: true, style } }];

Queuing for High Traffic

If your bot gets popular, you can't render 50 videos simultaneously. Implement a queue:

const staticData = $getWorkflowStaticData('global');
if (!staticData.queue) staticData.queue = [];

const request = {
  chatId: $input.first().json.message.chat.id,
  videoJson: $input.first().json.videoJson,
  requestedAt: Date.now(),
  position: staticData.queue.length + 1
};

staticData.queue.push(request);

// Process max 3 concurrent renders
const activeRenders = staticData.queue.filter(r => r.status === 'rendering').length;

if (activeRenders < 3) {
  request.status = 'rendering';
  return [{ json: { process: true, ...request } }];
} else {
  return [{
    json: {
      process: false,
      chatId: request.chatId,
      message: `You're #${request.position} in queue. Estimated wait: ${request.position * 30}s`
    }
  }];
}

Step 6: Use Cases in Production

Demo Video Generator

This is the highest-value use case. Sales teams share a bot link with prospects. The prospect sends their company name, the bot generates a personalized demo video showing how the product would look with their branding.

The workflow fetches the company's logo via Clearbit API, pulls their brand colors, and generates a custom demo. Conversion rates on personalized demos are 2-3x higher than generic ones.

Daily Content Bot

Build a bot that sends subscribers a daily video. Combine a Schedule trigger with the Telegram bot:

  1. Schedule trigger fires at 8:00 AM
  2. Fetch today's content (news, horoscope, stock data, weather)
  3. Build video JSON from the data
  4. Render the video
  5. Loop through subscriber list and send to each

Store subscriber chat IDs when users send /subscribe. Remove them on /unsubscribe.

Product Showcase Bot for E-commerce

Customers send a product name or SKU. The bot searches your catalog, pulls product images and descriptions, and generates a 15-second showcase video. Works as a pre-purchase tool in group chats or customer support channels.

Scaling and Rate Limits

Telegram's bot API allows approximately 30 messages per second. For most use cases, the bottleneck is video rendering, not Telegram.

Rendering capacity: Your subscription plan determines concurrent renders. The free tier handles 3 concurrent renders. Pro handles 10+.

File size limits: Telegram allows videos up to 50MB via the bot API (2GB via the upload method). Keep videos under 60 seconds at 1080p to stay well within limits.

Webhook vs polling: Use webhook mode in production (n8n's Telegram Trigger does this automatically). Polling mode adds latency and wastes resources.

Monitoring Your Bot

Track these metrics in a Google Sheet or database:

// Add this logging node at the end of every successful render
const logEntry = {
  timestamp: new Date().toISOString(),
  userId: $('Telegram Trigger').first().json.message.from.id,
  command: $('Telegram Trigger').first().json.message.text,
  renderTime: $json.render_time,
  status: "success",
  videoUrl: $json.output_url
};

return [{ json: logEntry }];

Set up alerts for: - Render failure rate above 5% - Average render time above 120 seconds - No requests for 24+ hours (bot might be disconnected)

The Complete n8n Workflow JSON

You can import this workflow directly into n8n. Go to Workflows > Import from File and paste the JSON. Update credentials, and the bot is live.

The full workflow JSON and pre-built templates for all the use cases above are available in the Template Marketplace. Search for "Telegram" to find them.

For more automation patterns, check out the automation guides -- including a dedicated guide for the Telegram demo video generator workflow.

Wrapping Up

You now have a Telegram bot that generates videos on demand. The core pattern -- trigger, build JSON, render, deliver -- applies to any messaging platform. Swap the Telegram nodes for Discord, Slack, or WhatsApp, and the rest of the workflow stays identical.

Start with the basic /demo command. Get it working end to end. Then add commands, inline keyboards, and user preferences. The compound effect of small improvements makes the difference between a toy demo and a tool people actually use.

Related Articles