AboutBlogContact
CRM & AutomationMay 1, 2026 9 min read 7

How to Build an Instagram Bot for Business in 2026

AunimedaAunimeda
📋 Table of Contents

How to Build an Instagram Bot for Business in 2026

There are two approaches to Instagram automation. The grey-zone approach uses browser automation or scraping libraries to simulate human actions — it works until Meta bans your account, usually within weeks. The right approach uses the official Meta Graph API, which is slower to set up but runs indefinitely without risk.

This guide covers the official approach: Instagram Graph API + Webhooks, handling DMs, comment replies, and story mentions.


What the Official API Allows

The Instagram Graph API (part of Meta's platform) allows:

  • Receive and reply to DMs (Instagram Messaging)
  • Reply to comments on your posts
  • Receive story mention notifications and respond
  • Send template messages (text, buttons, quick replies)
  • Get user profile info (name, username) of users who message you

What requires special Meta review approval:

  • Sending proactive DMs to users who haven't messaged you first
  • Bulk messaging campaigns

For most business use cases (autoresponders, lead capture via DM, comment management), no special approval beyond basic Business account verification is needed.


Architecture

Instagram User
      ↓ (sends DM / comments)
Meta Servers
      ↓ (webhook POST)
Your Server (HTTPS)
      ↓
Message Handler
      ↓
Meta Graph API (send reply)
      ↓
Instagram User receives reply

Step 1: Meta App Setup

  1. Go to developers.facebook.com
  2. Create a new App → Business type
  3. Add Instagram Graph API product
  4. Connect your Instagram Professional account (Business or Creator)
  5. Set up a webhook (explained below)

Required permissions for your app:

  • instagram_basic
  • instagram_manage_messages
  • instagram_manage_comments
  • pages_manage_metadata

For development, use Meta's Test Users so you don't need app review.


Step 2: Webhook Setup

Meta sends all events (DMs, comments, story mentions) to your webhook URL.

// src/routes/instagram-webhook.ts
import { Router, Request, Response } from 'express';
import crypto from 'crypto';

export const instagramWebhookRouter = Router();

const VERIFY_TOKEN = process.env.META_VERIFY_TOKEN!;
const APP_SECRET = process.env.META_APP_SECRET!;

// Webhook verification (one-time, during setup)
instagramWebhookRouter.get('/webhook', (req: Request, res: Response) => {
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === VERIFY_TOKEN) {
    console.log('Webhook verified');
    return res.status(200).send(challenge);
  }

  res.sendStatus(403);
});

// Receive events
instagramWebhookRouter.post('/webhook', (req: Request, res: Response) => {
  // Always respond 200 immediately — Meta retries if you're slow
  res.sendStatus(200);

  // Verify the request came from Meta
  if (!verifySignature(req)) {
    console.error('Invalid webhook signature');
    return;
  }

  const body = req.body;
  if (body.object !== 'instagram') return;

  for (const entry of body.entry ?? []) {
    // Direct Messages
    for (const messagingEvent of entry.messaging ?? []) {
      handleIncomingMessage(messagingEvent).catch(console.error);
    }

    // Comments
    for (const change of entry.changes ?? []) {
      if (change.field === 'comments') {
        handleComment(change.value).catch(console.error);
      }
      if (change.field === 'mentions') {
        handleMention(change.value).catch(console.error);
      }
    }
  }
});

function verifySignature(req: Request): boolean {
  const signature = req.headers['x-hub-signature-256'] as string;
  if (!signature) return false;

  const expected = crypto
    .createHmac('sha256', APP_SECRET)
    .update(JSON.stringify(req.body))
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature.replace('sha256=', '')),
    Buffer.from(expected)
  );
}

Step 3: Send Messages via Graph API

// src/services/instagram.service.ts
const GRAPH_API_BASE = 'https://graph.instagram.com/v21.0';
const PAGE_ACCESS_TOKEN = process.env.META_PAGE_ACCESS_TOKEN!;

interface SendMessageOptions {
  recipientId: string;
  text?: string;
  buttons?: Array<{ type: 'postback' | 'web_url'; title: string; payload?: string; url?: string }>;
  quickReplies?: Array<{ title: string; payload: string }>;
}

export async function sendInstagramMessage(options: SendMessageOptions): Promise<void> {
  const message: Record<string, unknown> = {};

  if (options.buttons && options.buttons.length > 0) {
    message.attachment = {
      type: 'template',
      payload: {
        template_type: 'button',
        text: options.text,
        buttons: options.buttons.map(btn => ({
          type: btn.type,
          title: btn.title,
          ...(btn.payload ? { payload: btn.payload } : {}),
          ...(btn.url ? { url: btn.url } : {}),
        })),
      },
    };
  } else {
    message.text = options.text;
  }

  if (options.quickReplies) {
    message.quick_replies = options.quickReplies.map(qr => ({
      content_type: 'text',
      title: qr.title,
      payload: qr.payload,
    }));
  }

  const response = await fetch(`${GRAPH_API_BASE}/me/messages`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${PAGE_ACCESS_TOKEN}`,
    },
    body: JSON.stringify({
      recipient: { id: options.recipientId },
      message,
      messaging_type: 'RESPONSE',
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Meta API error: ${JSON.stringify(error)}`);
  }
}

export async function replyToComment(commentId: string, message: string): Promise<void> {
  const response = await fetch(`${GRAPH_API_BASE}/${commentId}/replies`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${PAGE_ACCESS_TOKEN}`,
    },
    body: JSON.stringify({ message }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Comment reply error: ${JSON.stringify(error)}`);
  }
}

Step 4: Conversation State with Redis

Multi-step flows (lead capture, order intake) need to remember where each user is in the conversation.

// src/services/conversation.service.ts
import { createClient } from 'redis';

const redis = createClient({ url: process.env.REDIS_URL });
redis.connect().catch(console.error);

interface ConversationState {
  step: string;
  data: Record<string, string>;
  updatedAt: number;
}

const CONVERSATION_TTL = 60 * 60; // 1 hour idle timeout

export async function getState(userId: string): Promise<ConversationState | null> {
  const raw = await redis.get(`ig:conv:${userId}`);
  return raw ? JSON.parse(raw) : null;
}

export async function setState(userId: string, state: ConversationState): Promise<void> {
  await redis.setEx(
    `ig:conv:${userId}`,
    CONVERSATION_TTL,
    JSON.stringify({ ...state, updatedAt: Date.now() })
  );
}

export async function clearState(userId: string): Promise<void> {
  await redis.del(`ig:conv:${userId}`);
}

Step 5: Build a Lead Capture Flow

A common use case: user sends "price" or "info" → bot collects their name + phone number → sends to CRM.

// src/handlers/dm-handler.ts
import { sendInstagramMessage } from '../services/instagram.service';
import { getState, setState, clearState } from '../services/conversation.service';
import { saveLead } from '../services/crm.service';

interface IncomingMessage {
  sender: { id: string };
  recipient: { id: string };
  timestamp: number;
  message: {
    mid: string;
    text?: string;
    quick_reply?: { payload: string };
  };
}

const TRIGGER_KEYWORDS = ['price', 'цена', 'стоимость', 'info', 'buy', 'купить', 'order', 'заказать'];

export async function handleIncomingMessage(event: IncomingMessage): Promise<void> {
  const userId = event.sender.id;
  const text = event.message.text?.toLowerCase().trim() ?? '';
  const quickReplyPayload = event.message.quick_reply?.payload;

  const state = await getState(userId);

  // Handle quick reply responses
  if (quickReplyPayload) {
    await handleQuickReply(userId, quickReplyPayload, state);
    return;
  }

  // No active conversation — check for trigger keywords
  if (!state) {
    if (TRIGGER_KEYWORDS.some(kw => text.includes(kw))) {
      await startLeadFlow(userId);
    } else {
      await sendDefaultReply(userId);
    }
    return;
  }

  // Continue active conversation
  await continueLeadFlow(userId, text, state);
}

async function startLeadFlow(userId: string): Promise<void> {
  await setState(userId, { step: 'ask_name', data: {}, updatedAt: Date.now() });

  await sendInstagramMessage({
    recipientId: userId,
    text: "Hi! I'd love to help you. What's your name?",
    quickReplies: [
      { title: 'Skip', payload: 'SKIP_NAME' },
    ],
  });
}

async function continueLeadFlow(
  userId: string,
  text: string,
  state: { step: string; data: Record<string, string> }
): Promise<void> {
  switch (state.step) {
    case 'ask_name': {
      const name = text.length > 0 ? text : 'Unknown';
      await setState(userId, { step: 'ask_phone', data: { name }, updatedAt: Date.now() });

      await sendInstagramMessage({
        recipientId: userId,
        text: `Nice to meet you, ${name}! What's your phone number so we can call you back?`,
        quickReplies: [
          { title: 'Skip', payload: 'SKIP_PHONE' },
        ],
      });
      break;
    }

    case 'ask_phone': {
      const phone = text;
      const { name } = state.data;

      await completeLead(userId, name, phone);
      break;
    }
  }
}

async function handleQuickReply(
  userId: string,
  payload: string,
  state: { step: string; data: Record<string, string> } | null
): Promise<void> {
  if (payload === 'SKIP_NAME') {
    await setState(userId, { step: 'ask_phone', data: { name: 'Unknown' }, updatedAt: Date.now() });
    await sendInstagramMessage({
      recipientId: userId,
      text: "No worries! What's your phone number?",
    });
  }

  if (payload === 'SKIP_PHONE') {
    const name = state?.data.name ?? 'Unknown';
    await completeLead(userId, name, '');
  }
}

async function completeLead(userId: string, name: string, phone: string): Promise<void> {
  // Save to your CRM
  await saveLead({
    instagramUserId: userId,
    name,
    phone,
    source: 'instagram_dm',
    createdAt: new Date(),
  });

  await clearState(userId);

  await sendInstagramMessage({
    recipientId: userId,
    text: '✅ Thank you! Our manager will contact you within 1 hour.',
    buttons: [
      {
        type: 'web_url',
        title: 'View Our Services',
        url: 'https://aunimeda.com/services',
      },
    ],
  });
}

async function sendDefaultReply(userId: string): Promise<void> {
  await sendInstagramMessage({
    recipientId: userId,
    text: 'Hi! 👋 How can we help you today?',
    quickReplies: [
      { title: '💰 Pricing', payload: 'INTENT_PRICE' },
      { title: '📋 Services', payload: 'INTENT_SERVICES' },
      { title: '📞 Call Me', payload: 'INTENT_CALL' },
    ],
  });
}

Step 6: Comment Auto-Reply

Automatically reply to comments that contain specific keywords — useful for giveaways ("Comment PRICE to get a quote in DM") or product inquiries.

// src/handlers/comment-handler.ts
import { replyToComment, sendInstagramMessage } from '../services/instagram.service';

interface CommentEvent {
  id: string;
  text: string;
  from: { id: string; username: string };
  media: { id: string };
  timestamp: string;
}

const COMMENT_TRIGGERS: Array<{ keywords: string[]; reply: string; sendDm?: string }> = [
  {
    keywords: ['price', 'cost', 'how much', 'цена', 'сколько'],
    reply: 'Hi @{username}! 👋 Check your DMs — we sent you the details.',
    sendDm: "Hi! You asked about pricing in our comments. Here's what we offer:\n\n💼 Telegram Bot: from $800\n🌐 Web App: from $1,500\n📱 Mobile App: from $3,000\n\nReply here to discuss your project!",
  },
  {
    keywords: ['dm', 'inbox', 'message me'],
    reply: 'Hey @{username}! Check your DMs 📩',
    sendDm: "Hi! You wanted to connect — how can we help you?",
  },
];

export async function handleComment(event: CommentEvent): Promise<void> {
  const commentText = event.text.toLowerCase();
  const username = event.from.username;

  for (const trigger of COMMENT_TRIGGERS) {
    if (trigger.keywords.some(kw => commentText.includes(kw))) {
      // Reply publicly on the comment
      const publicReply = trigger.reply.replace('{username}', username);
      await replyToComment(event.id, publicReply);

      // Send private DM if configured
      if (trigger.sendDm) {
        await sendInstagramMessage({
          recipientId: event.from.id,
          text: trigger.sendDm,
        });
      }

      break; // Only apply first matching trigger
    }
  }
}

Step 7: Story Mention Response

When users tag your account in their story — a high-engagement moment — you can automatically respond:

// src/handlers/mention-handler.ts
import { sendInstagramMessage } from '../services/instagram.service';

interface MentionEvent {
  media_id: string;
  comment_id?: string;
  sender_id: string;
}

export async function handleMention(event: MentionEvent): Promise<void> {
  await sendInstagramMessage({
    recipientId: event.sender_id,
    text: "Thanks for mentioning us in your story! 🙌\n\nWant us to feature your story on our page?",
    quickReplies: [
      { title: '✅ Yes, please!', payload: 'REPOST_YES' },
      { title: '❌ No thanks', payload: 'REPOST_NO' },
    ],
  });
}

Deployment Checklist

  • Meta App in Live Mode (not Development) before going to production
  • Webhook URL is HTTPS with a valid SSL certificate
  • APP_SECRET and PAGE_ACCESS_TOKEN stored as environment variables, not in code
  • Respond to webhook events within 200ms (process async, respond 200 immediately)
  • Handle duplicate events (Meta may send the same event twice — use mid for deduplication)
  • Set up token refresh — Page Access Tokens expire, use long-lived tokens or system users
  • Log all incoming events for debugging

Aunimeda builds Instagram automation systems using the official Meta Graph API — DM flows, lead capture, comment management, and CRM integration.

Contact us to discuss your Instagram bot. See also: Instagram Bot Development, WhatsApp Bot Development, Business Automation

Read Also

How to Build a Telegram Mini App for Business in 2026aunimeda
CRM & Automation

How to Build a Telegram Mini App for Business in 2026

Telegram Mini Apps put a full web app inside Telegram with zero install friction. 900M users, seamless payments, native UI components. Here's how to build one from scratch.

How to Build a WhatsApp Bot for Business in 2026: Complete Guideaunimeda
CRM & Automation

How to Build a WhatsApp Bot for Business in 2026: Complete Guide

WhatsApp has 2.7 billion active users. Here's how to build a bot that handles customer support, orders, and lead qualification automatically — without annoying your customers.

Telegram Bot vs WhatsApp Bot: Which to Build for CIS Markets (2026)aunimeda
CRM & Automation

Telegram Bot vs WhatsApp Bot: Which to Build for CIS Markets (2026)

Detailed comparison of Telegram and WhatsApp bots for Russian, Kazakh, and Kyrgyz markets. Audience data, technical capabilities, costs, and when to build each.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Free consultation.

Get Consultation All articles