Comment construire un auto-responder Instagram DM sans outils tiers
13 mai 2026
·
22 min de lecture
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)
Configuration de l'App Facebook et connexion Instagram
Allez sur developers.facebook.com → Créer une App → Type Business
Ajoutez le produit Instagram à votre App
Sous Instagram → Settings, connectez votre compte Instagram Professional
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
Copiez le token généré dans IG_PAGE_TOKEN
Interrogez GET /me?fields=instagram_business_account — le instagram_business_account.id renvoyé est votre IG_BUSINESS_ID
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.
2
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.
3
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');
functionverifyInstagramSignature(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 };
4
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 processingconst body = req.body;
if (body.object !== 'instagram') return; // critical check — not "page" like Messengerfor (const entry of body.entry) {
for (const event of (entry.messaging || [])) {
const igsid = event.sender.id; // Instagram-Scoped ID — store as stringtry {
// Route by event typeif (event.message?.reply_to?.story) {
// User replied to your story via DMawaitrouteMessage(igsid, event.message.text, 'story_reply', event.message.reply_to.story);
} else if (event.referral?.source === 'STORY_MENTION') {
// User mentioned your account in their storyawaitrouteMessage(igsid, null, 'story_mention', event.referral.story);
} else if (event.message?.text) {
// Standard text DMawaitrouteMessage(igsid, event.message.text, 'text');
} else if (event.message?.attachments) {
// Image, video, audio, or sticker sent in DMawaitrouteMessage(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);
}
}
}
});
5
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
},
];
functionmatchRule(text) {
if (!text) returnnull;
for (const rule of RULES) {
if (rule.patterns.some(p => p.test(text))) {
return rule.response;
}
}
returnnull; // no rule matched → use GPT-4o
}
module.exports = { matchRule, RULES };
6
GPT-4o pour des réponses intelligentes
Node.js — GPT-4o integration with typing indicator
aiReply.js
const OpenAI = require('openai');
const oai = newOpenAI({ 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 functiongetAIReply(igsid, userMessage, conversationHistory) {
// Send typing indicator first — makes the bot feel humanawaitsendTypingIndicator(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 functionsendTypingIndicator(igsid) {
awaitfetch(
`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 };
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');
constGRAPH = 'https://graph.facebook.com/v21.0';
constBIZ_ID = process.env.IG_BUSINESS_ID;
constTOKEN = process.env.IG_PAGE_TOKEN;
async functionsendDM(igsid, text) {
const res = awaitfetch(`${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 functionrouteMessage(igsid, text, eventType, extra) {
const conv = awaitgetConversation(igsid);
// If user is in HANDOFF state, don't auto-reply — human handles itif (conv.state === 'HANDOFF') {
console.log(`User ${igsid} is in HANDOFF — skipping auto-reply`);
return;
}
// Story mention — thank them automaticallyif (eventType === 'story_mention') {
awaitsendDM(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 contextif (eventType === 'story_reply' && !text) {
awaitsendDM(igsid, "Hey! Thanks for reacting to our story 😍 Anything you'd like to know?");
return;
}
// Media sent (image/video) — acknowledgeif (eventType === 'media') {
awaitsendDM(igsid, "Thanks for sharing! 📸 How can we help you today?");
return;
}
// Save inbound message to historyawaitaddToHistory(igsid, 'user', text);
// 1. Try rule match first (fast, free)const ruleResponse = matchRule(text);
if (ruleResponse === 'HANDOFF') {
awaithandleEscalation(igsid);
return;
}
if (ruleResponse) {
awaitsendDM(igsid, ruleResponse);
awaitaddToHistory(igsid, 'assistant', ruleResponse);
return;
}
// 2. Fall back to GPT-4o for anything elseconst updatedConv = awaitgetConversation(igsid);
const aiReply = awaitgetAIReply(igsid, text, updatedConv.history);
if (aiReply.includes('[HANDOFF]')) {
awaithandleEscalation(igsid);
return;
}
awaitsendDM(igsid, aiReply);
awaitaddToHistory(igsid, 'assistant', aiReply);
}
module.exports = { routeMessage, sendDM };
9
Escalade vers un humain : quand le bot ne peut pas aider
Node.js — human escalation with Slack notification
escalation.js
const { sendDM } = require('./autoResponder');
const { setState } = require('./conversationState');
async functionhandleEscalation(igsid) {
// Tell the user a human is on the wayawaitsendDM(igsid,
"Let me connect you with a team member who can help! 👋\n" +
"Someone will reply to you within 1 business hour during Mon–Fri 9am–6pm EST.\n" +
"Anything else I can answer in the meantime? 😊"
);
// Set conversation state to HANDOFF — bot stops auto-replyingawaitsetState(igsid, 'HANDOFF');
// Notify your team via Slackif (process.env.SLACK_WEBHOOK_URL) {
awaitfetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `🔔 Instagram DM escalation needed\nIGSID: ${igsid}\nCheck Instagram DMs to respond.`
}),
});
}
console.log(`Escalated IGSID ${igsid} to human agent`);
}
module.exports = { handleEscalation };
10
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') {
awaitrouteMessage(igsid, message.body, 'text');
} else if (event === 'story.reply') {
awaitrouteMessage(igsid, message.body, 'story_reply', story);
} else if (event === 'story.mention') {
awaitrouteMessage(igsid, null, 'story_mention', story);
}
});
// Total handler: 12 lines vs 80+ lines of raw webhook parsing and verification
FAQ
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.
Arrêtez de gérer les API Meta. Commencez à construire.
Connectez votre premier compte Facebook, Instagram ou WhatsApp en moins de 2 minutes. Votre webhook reçoit son premier payload avant que votre café refroidisse.