Auto-responder de Instagram DM creado sin herramientas de terceros — configuración de webhook, verificación HMAC, enrutamiento basado en reglas, respuestas con IA GPT-4o, máquina de estados de conversación en Redis
Lo que vas a construir: Un auto-responder de Instagram DM listo para producción que gestiona texto, respuestas a stories y menciones en stories — con respuestas rápidas basadas en reglas para las preguntas frecuentes, GPT-4o para todo lo demás, Redis para el estado de la conversación y escalado a humano cuando hace falta. Solo API oficial. Cero plataformas de terceros.

¿Por qué construir el tuyo en lugar de usar una herramienta de terceros?

ManyChat, Interakt y plataformas similares son la opción correcta si necesitas tener algo funcionando en una tarde sin escribir código. Son la opción equivocada cuando necesitas:

  • Lógica de negocio a medida. Conectar tu bot de DM a tus sistemas internos — tu base de datos, tu CRM, tu tienda de Shopify — requiere código. Las herramientas no-code no pueden hacer integraciones arbitrarias por API.
  • Propiedad de los datos. Las conversaciones con tus clientes son tuyas. En ManyChat viven en la base de datos de ManyChat. En tu propio stack viven donde tú quieras.
  • Sin tarifas por contacto. ManyChat cobra mensualmente según el número de contactos. Tu propio código no tiene coste por contacto — solo el hosting de tu servidor.
  • Sin riesgo de plataforma. Las herramientas de terceros pueden cambiar precios, añadir topes o eliminar funciones. Tu propio código solo cambia cuando tú lo cambias.

Requisitos previos

  • Cuenta profesional de Instagram (Business o Creator) vinculada a una Facebook Page que administres
  • Node.js 18+ instalado en local
  • URL HTTPS pública para desarrollo local — usa ngrok: ngrok http 3000
  • API key de OpenAI (para el Paso 6 con GPT-4o — opcional)
  • Redis (para el Paso 7 de estado de conversación — opcional)
Shell — project setup
mkdir instagram-auto-responder && cd instagram-auto-responder npm init -y npm install express dotenv openai ioredis cat > .env << EOF IG_VERIFY_TOKEN=your_verify_secret_here IG_APP_SECRET=your_app_secret_here IG_PAGE_TOKEN=your_page_access_token_here IG_BUSINESS_ID=your_instagram_business_account_id OPENAI_API_KEY=your_openai_key_here REDIS_URL=redis://localhost:6379 PORT=3000 EOF
Configuración de la Facebook App y conexión con Instagram
  1. Ve a developers.facebook.com → Create App → tipo Business
  2. Añade el producto Instagram a tu app
  3. En Instagram → Settings, conecta tu cuenta profesional de Instagram
  4. Graph API Explorer → selecciona tu App → selecciona tu Facebook Page → Generate Token con: instagram_manage_messages + pages_messaging + pages_read_engagement
  5. Copia el token generado como IG_PAGE_TOKEN
  6. Consulta GET /me?fields=instagram_business_account — el instagram_business_account.id devuelto es tu IG_BUSINESS_ID
  7. Copia el App Secret desde Settings → Basic como IG_APP_SECRET
App Review para producción: Durante el desarrollo, la API funciona con las cuentas añadidas como Test Users en la configuración de tu App. Para producción (responder a clientes reales que no se hayan añadido como test users), debes enviar la solicitud a App Review con instagram_manage_messages. Prepara una grabación de pantalla mostrando tu bot y una descripción clara del caso de uso. La revisión suele tardar entre 5 y 10 días hábiles.
Endpoint de verificación del webhook
Node.js + Express — webhook verification
index.js
require('dotenv').config(); const express = require('express'); const crypto = require('crypto'); const app = express(); // CRITICAL: raw body needed for HMAC verification — must come BEFORE express.json() app.use('/webhook', express.raw({ type: 'application/json' })); app.use(express.json()); // GET /webhook — Instagram challenge handshake (same pattern as Messenger) app.get('/webhook', (req, res) => { const mode = req.query['hub.mode']; const token = req.query['hub.verify_token']; const challenge = req.query['hub.challenge']; if (mode === 'subscribe' && token === process.env.IG_VERIFY_TOKEN) { console.log('✓ Instagram webhook verified'); return res.status(200).send(challenge); } res.sendStatus(403); }); app.listen(process.env.PORT || 3000, () => console.log('🤖 Instagram auto-responder running'));

Tras lanzar tu servidor, ve a tu Facebook App → Instagram → Webhooks → Add Callback URL. Introduce tu URL de ngrok + /webhook y tu verify token. Suscríbete a: messages y messaging_referrals.

Verificación de firma HMAC-SHA256 — nunca te la saltes

Cada POST de Instagram incluye X-Hub-Signature-256. Si no lo verificas, cualquiera que conozca tu URL puede falsificar eventos. El middleware express.raw() (configurado en el Paso 2) proporciona el Buffer en bruto necesario para calcular el HMAC.

Node.js — HMAC signature middleware
verifySignature.js
const crypto = require('crypto'); function verifyInstagramSignature(req, res, next) { const signature = req.headers['x-hub-signature-256']; if (!signature) return res.sendStatus(401); const hash = crypto .createHmac('sha256', process.env.IG_APP_SECRET) .update(req.body) // req.body is raw Buffer from express.raw() .digest('hex'); const expected = Buffer.from(signature.split('=')[1], 'hex'); const computed = Buffer.from(hash, 'hex'); if (expected.length !== computed.length || !crypto.timingSafeEqual(expected, computed)) { return res.sendStatus(401); } // Parse body after verification — it was raw Buffer before req.body = JSON.parse(req.body.toString('utf8')); next(); } module.exports = { verifyInstagramSignature };
Handler completo del evento POST del webhook
Node.js — full Instagram event handler
webhookHandler.js
const { verifyInstagramSignature } = require('./verifySignature'); const { routeMessage } = require('./autoResponder'); app.post('/webhook', verifyInstagramSignature, async (req, res) => { res.sendStatus(200); // respond immediately — always before processing const body = req.body; if (body.object !== 'instagram') return; // critical check — not "page" like Messenger for (const entry of body.entry) { for (const event of (entry.messaging || [])) { const igsid = event.sender.id; // Instagram-Scoped ID — store as string try { // Route by event type if (event.message?.reply_to?.story) { // User replied to your story via DM await routeMessage(igsid, event.message.text, 'story_reply', event.message.reply_to.story); } else if (event.referral?.source === 'STORY_MENTION') { // User mentioned your account in their story await routeMessage(igsid, null, 'story_mention', event.referral.story); } else if (event.message?.text) { // Standard text DM await routeMessage(igsid, event.message.text, 'text'); } else if (event.message?.attachments) { // Image, video, audio, or sticker sent in DM await routeMessage(igsid, null, 'media', event.message.attachments[0]); } else if (event.reaction) { // Emoji reaction — usually no response needed console.log(`Reaction from ${igsid}: ${event.reaction.reaction} (${event.reaction.action})`); } } catch (err) { console.error(`Error handling event for ${igsid}:`, err.message); } } } });
Motor de auto-responder basado en reglas

Respuestas rápidas y deterministas para las preguntas frecuentes — sin coste de inferencia de IA, respuesta instantánea. Los patrones regex detectan intenciones específicas; cada uno se asocia a una respuesta predefinida. GPT-4o se encarga de cualquier cosa que no coincida.

Node.js — rule-based responder + GPT-4o fallback
autoResponder.js
const RULES = [ { patterns: [/^(hi|hello|hey|hola|yo|sup)[\s!]*$/i, /^good (morning|afternoon|evening)/i], response: "Hey! 👋 Thanks for reaching out. How can we help you today?", }, { patterns: [/price|how much|cost|pricing/i], response: "Great question! Our pricing is on our website 💛\nCheck it out: https://yoursite.com/pricing\n\nAny other questions?", }, { patterns: [/ship.{0,20}(to|time|free|worldwide)/i, /delivery|deliver/i], response: "We ship worldwide! 🌍\n• USA: 3–5 business days\n• International: 7–14 days\nFree shipping on orders over $50 🎉", }, { patterns: [/return|refund|exchange/i], response: "We accept returns within 30 days of delivery 📦\nItems must be unused and in original packaging.\nStart here: https://yoursite.com/returns", }, { patterns: [/hours|open|when.{0,10}(open|close|available)/i], response: "Our support team is available Mon–Fri, 9am–6pm EST 🕐\nFor urgent questions, reply here and we'll get back to you ASAP!", }, { patterns: [/(human|person|agent|someone|representative)/i, /talk to.{0,10}(human|person|real)/i], response: "HANDOFF", // special signal — triggers escalation flow }, ]; function matchRule(text) { if (!text) return null; for (const rule of RULES) { if (rule.patterns.some(p => p.test(text))) { return rule.response; } } return null; // no rule matched → use GPT-4o } module.exports = { matchRule, RULES };
GPT-4o para respuestas inteligentes
Node.js — GPT-4o integration with typing indicator
aiReply.js
const OpenAI = require('openai'); const oai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const IG_SYSTEM_PROMPT = `You are a helpful assistant for @yourbrand on Instagram. Answer questions about our products, services, shipping, and returns. Keep replies SHORT — Instagram DM users are on mobile. Max 150 words. Use plain text (no markdown). Warm, friendly, conversational tone. If you're unsure or the question is complex, say: [HANDOFF] Never make up information about products you don't know.`; async function getAIReply(igsid, userMessage, conversationHistory) { // Send typing indicator first — makes the bot feel human await sendTypingIndicator(igsid); const messages = [ { role: 'system', content: IG_SYSTEM_PROMPT }, ...conversationHistory.slice(-8), // last 8 exchanges for context { role: 'user', content: userMessage }, ]; const completion = await oai.chat.completions.create({ model: 'gpt-4o', messages, max_tokens: 200, temperature: 0.7, }); return completion.choices[0].message.content; } async function sendTypingIndicator(igsid) { await fetch( `https://graph.facebook.com/v21.0/${process.env.IG_BUSINESS_ID}/messages?access_token=${process.env.IG_PAGE_TOKEN}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ recipient: { id: igsid }, sender_action: 'typing_on' }), } ); } module.exports = { getAIReply, sendTypingIndicator };
Máquina de estados de conversación en Redis
Node.js + Redis — conversation state per IGSID
conversationState.js
const { Redis } = require('ioredis'); const redis = new Redis(process.env.REDIS_URL); const TTL = 3600; // 1 hour — reset state if user goes quiet async function getConversation(igsid) { const data = await redis.get(`ig:conv:${igsid}`); return data ? JSON.parse(data) : { state: 'ACTIVE', history: [] }; } async function saveConversation(igsid, conversation) { await redis.setex(`ig:conv:${igsid}`, TTL, JSON.stringify(conversation)); } async function addToHistory(igsid, role, content) { const conv = await getConversation(igsid); conv.history.push({ role, content, ts: Date.now() }); // Keep last 20 messages to prevent unbounded growth if (conv.history.length > 20) conv.history = conv.history.slice(-20); await saveConversation(igsid, conv); return conv; } async function setState(igsid, state) { const conv = await getConversation(igsid); conv.state = state; await saveConversation(igsid, conv); } module.exports = { getConversation, saveConversation, addToHistory, setState };
Send API de Instagram + la función routeMessage completa
Node.js — complete routeMessage function
autoResponder.js
const { matchRule } = require('./rules'); const { getAIReply, sendTypingIndicator } = require('./aiReply'); const { getConversation, addToHistory, setState } = require('./conversationState'); const GRAPH = 'https://graph.facebook.com/v21.0'; const BIZ_ID = process.env.IG_BUSINESS_ID; const TOKEN = process.env.IG_PAGE_TOKEN; async function sendDM(igsid, text) { const res = await fetch(`${GRAPH}/${BIZ_ID}/messages?access_token=${TOKEN}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ recipient: { id: igsid }, message: { text } }), }); if (!res.ok) console.error('Send failed:', await res.json()); } async function routeMessage(igsid, text, eventType, extra) { const conv = await getConversation(igsid); // If user is in HANDOFF state, don't auto-reply — human handles it if (conv.state === 'HANDOFF') { console.log(`User ${igsid} is in HANDOFF — skipping auto-reply`); return; } // Story mention — thank them automatically if (eventType === 'story_mention') { await sendDM(igsid, "We saw you mentioned us — thank you so much! 🙏✨ We might repost it. Cool with you? Just reply 'YES' 😊"); return; } // Story reply — acknowledge the story context if (eventType === 'story_reply' && !text) { await sendDM(igsid, "Hey! Thanks for reacting to our story 😍 Anything you'd like to know?"); return; } // Media sent (image/video) — acknowledge if (eventType === 'media') { await sendDM(igsid, "Thanks for sharing! 📸 How can we help you today?"); return; } // Save inbound message to history await addToHistory(igsid, 'user', text); // 1. Try rule match first (fast, free) const ruleResponse = matchRule(text); if (ruleResponse === 'HANDOFF') { await handleEscalation(igsid); return; } if (ruleResponse) { await sendDM(igsid, ruleResponse); await addToHistory(igsid, 'assistant', ruleResponse); return; } // 2. Fall back to GPT-4o for anything else const updatedConv = await getConversation(igsid); const aiReply = await getAIReply(igsid, text, updatedConv.history); if (aiReply.includes('[HANDOFF]')) { await handleEscalation(igsid); return; } await sendDM(igsid, aiReply); await addToHistory(igsid, 'assistant', aiReply); } module.exports = { routeMessage, sendDM };
Escalado a humano: cuando el bot no puede ayudar
SocialHook: sáltate los Pasos 2 a 4 por completo

Los Pasos 2, 3 y 4 — verificación del webhook, validación de la firma HMAC y parseo del payload en bruto — son código repetitivo de infraestructura que cada integración con Instagram reconstruye desde cero. SocialHook se encarga de todo. Conecta tu cuenta de Instagram y cada evento llega pre-verificado y pre-parseado a tu endpoint. Tu routeMessage() de los Pasos 5 a 9 encaja directamente:

Node.js — minimal handler with SocialHook (no boilerplate)
minimal.js
// SocialHook version: no HMAC, no raw body, no object:instagram check // Every event arrives pre-verified and pre-classified app.post('/webhook', express.json(), async (req, res) => { res.sendStatus(200); const { event, from: igsid, message, story } = req.body; // event: "message.received" | "story.reply" | "story.mention" // message.body: the text content // story.storage_url: already downloaded (CDN expiry handled) if (event === 'message.received') { await routeMessage(igsid, message.body, 'text'); } else if (event === 'story.reply') { await routeMessage(igsid, message.body, 'story_reply', story); } else if (event === 'story.mention') { await routeMessage(igsid, null, 'story_mention', story); } }); // Total handler: 12 lines vs 80+ lines of raw webhook parsing and verification

Preguntas frecuentes

¿Construir un auto-responder de Instagram sin ManyChat viola los Términos de Servicio de Instagram?
No — construirlo con la API oficial de Instagram Messaging de Meta cumple totalmente. Lo que sí viola los ToS: automatización del navegador, scraping de tokens de sesión, APIs de scraping no oficiales y herramientas que simulan a un usuario logueado para enviar DMs a escala. La API oficial de Messaging es el método diseñado y aprobado por Meta para que las empresas automaticen los DMs de Instagram. Requiere una cuenta profesional de Instagram, App Review y concesión explícita de permisos.
¿Por qué debo usar express.raw() en lugar de express.json() para la verificación HMAC?
La verificación de firma HMAC-SHA256 requiere los bytes en bruto del cuerpo de la petición — exactamente como se recibieron por la red. express.json() parsea el cuerpo a un objeto JavaScript, que ya no puede reproducir los bytes originales para calcular el HMAC. express.raw({ type: 'application/json' }) te da un Buffer con el cuerpo en bruto. Calcula el HMAC sobre ese Buffer, verifica contra la cabecera de firma y luego parsea manualmente con JSON.parse(req.body.toString()).
¿Cómo gestiono las respuestas a stories de Instagram de forma distinta a los DMs normales?
Comprueba event.message.reply_to.story en tu handler del webhook. Cuando está presente, el evento de mensaje es una respuesta a una story — el usuario respondió a tu story por DM. El texto de la respuesta está en event.message.text. La URL del medio de la story está en event.message.reply_to.story.url (descárgala de inmediato — las URLs de CDN expiran en pocas horas). El auto-responder puede reconocer el contexto de la story de forma distinta a un DM en frío.
¿Cómo detengo las respuestas automáticas cuando un humano toma el control de una conversación?
Usa un estado en Redis por IGSID. Cuando se dispara el escalado (el usuario pide hablar con un humano o la IA incluye [HANDOFF]), guarda el estado de la conversación como "HANDOFF" en Redis. Al inicio de tu función routeMessage(), comprueba este estado — si es "HANDOFF", salta la respuesta automática por completo y retorna. Tu agente humano responde manualmente desde la app de Instagram. Restablece el estado a "ACTIVE" cuando el humano marque la conversación como resuelta, o automáticamente cuando expire el TTL de Redis.
¿Cómo evito respuestas automáticas duplicadas para el mismo mensaje?
Instagram puede reintentar la entrega del webhook si tu servidor es lento o devuelve un status distinto de 200. Usa el ID del mensaje (event.message.mid) como clave de deduplicación — guárdalo en Redis con un TTL corto (5 minutos) la primera vez que lo proceses. Al inicio de tu handler, comprueba si el mid ya está en Redis: si sí, salta el procesado y devuelve 200. Esto evita respuestas duplicadas por reintentos de webhook.
¿Necesito un servidor separado para cada cuenta de Instagram?
No — un solo servidor gestiona varias cuentas de Instagram. Los eventos de distintas cuentas llegan a la misma URL de webhook, diferenciados por entry[0].id (el Instagram Business Account ID). Tu handler busca el Page Access Token y el contexto del cliente correctos en una base de datos usando ese ID. Consulta la guía de webhooks multi-cuenta para ver la arquitectura completa de enrutamiento.

Tú escribes las reglas de negocio.
Nosotros nos encargamos de la capa de webhook.

Conecta tu cuenta de Instagram a SocialHook. Cada DM, respuesta a story y mención en story llega pre-verificado y normalizado a tu endpoint. Tu motor de reglas, tu integración con GPT-4o y tu máquina de estados en Redis encajan directamente. 12 líneas en lugar de 80+.

Sin tarjeta de crédito · $50/mes tras la prueba · Instagram + Messenger + WhatsApp