Auto-responder Instagram DM construit sans outils tiers — configuration des webhooks, vérification HMAC, routage basé sur des règles, réponses IA avec GPT-4o, machine à états de conversation Redis
Ce que vous allez construire : Un auto-responder Instagram DM prêt pour la production qui gère les messages texte, les réponses aux stories et les mentions dans les stories — avec des réponses rapides basées sur des règles pour les questions courantes, GPT-4o pour tout le reste, Redis pour l'état de la conversation et l'escalade vers un humain quand c'est nécessaire. Uniquement l'API officielle. Zéro plateforme tierce.

Pourquoi construire votre propre stack au lieu d'utiliser un outil tiers ?

ManyChat, Interakt et les plateformes similaires sont le bon choix si vous avez besoin de quelque chose de fonctionnel en un après-midi, sans coder. Ce sont les mauvais choix quand vous avez besoin de :

  • Logique métier personnalisée. Connecter votre bot DM à vos systèmes internes — votre base de données, votre CRM, votre boutique Shopify — nécessite du code. Les outils no-code ne peuvent pas faire des intégrations API arbitraires.
  • Propriété des données. Vos conversations clients vous appartiennent. Sur ManyChat, elles vivent dans la base de données de ManyChat. Sur votre propre stack, elles vivent là où vous le voulez.
  • Pas de frais par contact. ManyChat facture mensuellement en fonction du nombre de contacts. Votre propre code n'a aucun coût par contact — juste votre hébergement serveur.
  • Pas de risque plateforme. Les outils tiers peuvent changer leurs tarifs, ajouter des plafonds ou déprécier des fonctionnalités. Votre propre code ne change que lorsque vous le changez.

Prérequis

  • Compte Instagram Professional (Business ou Creator) lié à une Page Facebook dont vous êtes administrateur
  • Node.js 18+ installé en local
  • URL HTTPS publique pour le développement local — utilisez ngrok : ngrok http 3000
  • Clé API OpenAI (pour GPT-4o à l'étape 6 — optionnel)
  • Redis (pour l'état de la conversation à l'étape 7 — optionnel)
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
Configuration de l'App Facebook et connexion Instagram
  1. Allez sur developers.facebook.com → Créer une App → Type Business
  2. Ajoutez le produit Instagram à votre App
  3. Sous Instagram → Settings, connectez votre compte Instagram Professional
  4. Graph API Explorer → sélectionnez votre App → sélectionnez votre Page Facebook → Générez un token avec : instagram_manage_messages + pages_messaging + pages_read_engagement
  5. Copiez le token généré dans IG_PAGE_TOKEN
  6. Interrogez GET /me?fields=instagram_business_account — le instagram_business_account.id renvoyé est votre IG_BUSINESS_ID
  7. Copiez l'App Secret depuis Settings → Basic dans IG_APP_SECRET
App Review pour la production : Pendant le développement, l'API fonctionne pour les comptes ajoutés en tant que Test Users dans les paramètres de votre App. Pour la production (répondre à de vrais clients qui n'ont pas été ajoutés comme test users), vous devez soumettre votre App à l'App Review avec instagram_manage_messages. Préparez un enregistrement d'écran montrant votre bot et une description claire du cas d'usage. La review prend généralement 5 à 10 jours ouvrés.
Endpoint de vérification du 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'));

Après avoir lancé votre serveur, allez dans votre App Facebook → Instagram → Webhooks → Add Callback URL. Saisissez votre URL ngrok + /webhook et votre verify token. Abonnez-vous à : messages et messaging_referrals.

Vérification de signature HMAC-SHA256 — ne sautez jamais cette étape

Chaque POST d'Instagram inclut X-Hub-Signature-256. Sans le vérifier, quiconque connaît votre URL peut forger des événements. Le middleware express.raw() (configuré à l'étape 2) fournit le Buffer brut nécessaire au calcul 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 complet des événements POST du 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); } } } });
Moteur d'auto-responder basé sur des règles

Des réponses rapides et déterministes pour les questions courantes — aucun coût d'inférence IA, réponse instantanée. Des patterns regex correspondent à des intentions spécifiques ; chacun renvoie à une réponse pré-écrite. GPT-4o gère tout ce qui ne matche pas.

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 pour des réponses intelligentes
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 };
Machine à états de conversation 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 };
Instagram Send API et la fonction routeMessage complète
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 };
Escalade vers un humain : quand le bot ne peut pas aider
SocialHook : sautez complètement les étapes 2 à 4

Les étapes 2, 3 et 4 — vérification du webhook, validation de signature HMAC, parsing du payload brut — sont du boilerplate d'infrastructure que chaque intégration Instagram reconstruit de zéro. SocialHook gère tout cela. Connectez votre compte Instagram et chaque événement arrive pré-vérifié et pré-parsé à votre endpoint. Votre routeMessage() des étapes 5 à 9 se branche directement :

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

Questions fréquentes

Construire un auto-responder Instagram sans ManyChat viole-t-il les conditions d'utilisation d'Instagram ?
Non — construire avec l'API officielle Meta Instagram Messaging est totalement conforme. Ce qui viole les CGU : l'automatisation de navigateur, le scraping de tokens de session, les API scraper non officielles et les outils qui simulent un utilisateur connecté pour envoyer des DM à grande échelle. La Messaging API officielle est la méthode conçue et approuvée par Meta pour que les entreprises automatisent leurs DM Instagram. Elle nécessite un compte Instagram Professional, une App Review et des autorisations explicites.
Pourquoi dois-je utiliser express.raw() au lieu d'express.json() pour la vérification HMAC ?
La vérification de signature HMAC-SHA256 nécessite les bytes bruts du corps de la requête — exactement tels que reçus sur le réseau. express.json() parse le body en un objet JavaScript, qui ne peut plus produire les bytes originaux pour le calcul HMAC. express.raw({ type: 'application/json' }) vous donne un Buffer avec le body brut. Calculez le HMAC sur ce Buffer, vérifiez-le par rapport au header de signature, puis parsez manuellement avec JSON.parse(req.body.toString()).
Comment gérer les réponses aux stories Instagram différemment des DM classiques ?
Vérifiez la présence de event.message.reply_to.story dans votre handler de webhook. Quand il est présent, l'événement message est une réponse à une story — l'utilisateur a répondu à votre story par DM. Le texte de la réponse est dans event.message.text. L'URL du média de la story est dans event.message.reply_to.story.url (téléchargez-la immédiatement — les URL du CDN expirent en quelques heures). L'auto-responder peut reconnaître le contexte de la story différemment d'un DM à froid.
Comment arrêter les réponses automatiques quand un humain prend en charge une conversation ?
Utilisez un état Redis par IGSID. Quand une escalade est déclenchée (l'utilisateur demande un humain, ou l'IA inclut [HANDOFF]), fixez l'état de la conversation à "HANDOFF" dans Redis. En haut de votre fonction routeMessage(), vérifiez cet état — si "HANDOFF", sautez complètement la réponse automatique et retournez. Votre agent humain répond manuellement via l'application Instagram. Remettez l'état à "ACTIVE" quand l'humain marque la conversation comme résolue, ou automatiquement après l'expiration du TTL Redis.
Comment éviter les réponses automatiques dupliquées pour un même message ?
Instagram peut retenter la livraison du webhook si votre serveur est lent ou renvoie un statut autre que 200. Utilisez l'ID du message (event.message.mid) comme clé de déduplication — stockez-le dans Redis avec un TTL court (5 minutes) lors du premier traitement. Au début de votre handler, vérifiez si le mid est déjà dans Redis : si oui, sautez le traitement et retournez 200. Cela évite les réponses dupliquées dues aux retries du webhook.
Ai-je besoin d'un serveur séparé pour chaque compte Instagram ?
Non — un seul serveur gère plusieurs comptes Instagram. Les événements de différents comptes arrivent à la même URL de webhook, différenciés par entry[0].id (l'Instagram Business Account ID). Votre handler récupère le bon Page Access Token et le contexte client depuis une base de données en utilisant cet ID. Consultez le guide de webhook multi-comptes pour l'architecture de routage complète.

Vous écrivez les règles métier.
Nous gérons la couche webhook.

Connectez votre compte Instagram à SocialHook. Chaque DM, réponse à une story et mention dans une story arrive pré-vérifié et normalisé à votre endpoint. Votre moteur de règles, votre intégration GPT-4o et votre machine à états Redis se branchent directement. 12 lignes au lieu de 80 et plus.

Aucune carte bancaire requise · 50 $/mois après l'essai · Instagram + Messenger + WhatsApp