Webhooks de réponses aux stories Instagram : comment capturer et répondre automatiquement
13 mai 2026
·
16 min de lecture
Dans ce guide : Réponse de story vs mention de story — la distinction qui compte · Schémas exacts des payloads webhook pour les deux · L'abonnement messaging_referrals · L'expiration des URL CDN et comment la gérer · La fenêtre de 24 heures pour les réponses automatiques · Code complet du pipeline de réponse automatique (Node.js + Python) · Cas d'usage et modèles · Format normalisé SocialHook
Deux événements distincts : réponse de story vs mention de story
La première chose à comprendre : « interactions de stories Instagram » couvre deux événements webhook complètement différents, avec des structures de payload différentes, des abonnements webhook différents et des actions déclenchées différentes. Les confondre est la source n°1 de bugs dans les systèmes d'automatisation de stories.
Réponse de story
L'utilisateur répond à VOTRE story
« Vous publiez une story. Un abonné fait glisser vers le haut et envoie "C'est incroyable !" en réponse DM à votre story. »
Abonnement : messages
Type d'événement : événement message
Détection : event.message.reply_to.story
Chemin du payload : event.message.text (leur réponse)
URL de la story : event.message.reply_to.story.url
Mention de story
L'utilisateur VOUS mentionne dans SA story
« Un client publie une story montrant votre produit et identifie votre compte professionnel avec @votremarque. »
Abonnement : messaging_referrals
Type d'événement : événement referral
Détection : event.referral.source === 'STORY_MENTION'
Chemin du payload : event.referral.story.url
ID de la story : event.referral.story.id
Abonnements webhook : les champs dont vous avez besoin
C'est le détail de configuration le plus souvent oublié — chaque type d'événement de story nécessite son propre abonnement de champ webhook. Les deux doivent être activés dans votre Facebook App → Instagram → Webhooks → Modifier les abonnements.
Champ
Requis pour
Ce que vous manquez sans
messages
Requis
Tous les DMs, y compris les réponses aux stories. Sans cela, vous ne recevez aucun événement de message — ni DM texte, ni réponse de story, ni message média. C'est l'abonnement de base.
messaging_referrals
Requis
Mentions de story (quand les utilisateurs vous identifient dans leurs propres stories) et entrées Click-to-DM des publicités. Sans cela, les mentions de story disparaissent silencieusement — aucune erreur, aucun événement, juste rien.
messaging_optins
Optionnel
Événements d'opt-in lorsque les utilisateurs appuient sur Autoriser dans un widget d'opt-in pour notifications récurrentes. Non nécessaire pour les interactions de story.
Abonnez-vous aux deux dès maintenant. Si vous n'avez actuellement que messages activé, vous avez déjà manqué toutes les mentions de story arrivées sur votre compte. Il n'y a aucun moyen de les récupérer rétroactivement — les événements ont été déclenchés et perdus. Ajoutez messaging_referrals dans les paramètres de votre App immédiatement après avoir lu cette section.
Réponse de story : payload webhook exact
Lorsqu'un utilisateur répond à votre story Instagram via DM, l'événement arrive sous forme d'événement webhook messages standard — mais avec un objet reply_to supplémentaire imbriqué dans le message. C'est ainsi que vous distinguez une réponse de story d'un DM ordinaire.
Story reply — full webhook payload
{
"object": "instagram",
"entry": [{
"id": "987654321098765", // your IG Business Account ID"messaging": [{
"sender": { "id": "12345678901234" }, // IGSID of the user who replied"recipient": { "id": "987654321098765" },// your account ID"timestamp": 1747231892,
"message": {
"mid": "aWdtc2c_ZmlkPW...",
"text": "This is amazing! Where can I buy it? 😍", // their reply text"reply_to": { // ← presence of this object = it's a story reply"story": {
"id": "17893310459840806", // story ID (stable for 30 days)"url": "https://lookaside.fbsbx.com/..."// CDN URL — expires! download now
}
}
}
}]
}]
}
// If user replied with an emoji reaction instead of text, you get:// "message": { "mid": "...", "attachments": [{ "type": "like_heart", ... }], "reply_to": {...} }// If user replied with an image/video, attachments array contains the media
Mention de story : payload webhook exact
Lorsqu'un utilisateur identifie votre compte dans sa propre story, l'événement arrive sous forme d'événement referral — complètement séparé de l'objet message. La structure est nettement différente, ce qui explique pourquoi les deux sont souvent confondues.
Story mention — full webhook payload
{
"object": "instagram",
"entry": [{
"id": "987654321098765",
"messaging": [{
"sender": { "id": "12345678901234" }, // IGSID of user who mentioned you"recipient": { "id": "987654321098765" },
"timestamp": 1747231892,
"referral": { // ← presence of this = story mention or ad entry"source": "STORY_MENTION", // confirms this is a story mention (not an ad)"type": "OPEN_THREAD",
"story": {
"id": "17893310459898765", // the user's story ID"url": "https://lookaside.fbsbx.com/..."// CDN — download immediately!
}
},
"message": { // present but often empty for cold-start story mentions"mid": "aWdtc2c..."
}
}]
}]
}
// KEY DIFFERENCES from story reply:// 1. referral object present, not reply_to// 2. referral.source === "STORY_MENTION" (could also be "ADS" or "CUSTOMER_CHAT_PLUGIN")// 3. Story URL is at referral.story.url, not message.reply_to.story.url// 4. No reply text — user didn't type anything, they just tagged you
Le problème d'expiration de l'URL CDN — téléchargez immédiatement
Les payloads de réponse de story et de mention de story incluent tous deux une URL de story — un lien CDN pointant vers le média de la story (image ou vidéo). Cette URL est temporaire. Elle expire généralement quelques heures après le déclenchement du webhook. Si vous stockez l'URL et essayez de la charger plus tard — dans votre base de données, dans votre CRM, dans un message Slack — vous obtenez une erreur 403 ou 404.
La seule approche correcte : téléchargez immédiatement les octets du média de la story lorsque le webhook est déclenché, avant d'accuser réception ou de terminer le traitement de l'événement.
Node.js — download story media before it expires
downloadStory.js
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3 = newS3Client({ region: process.env.AWS_REGION });
async functiondownloadAndStoreStory(storyUrl, storyId) {
// Download story media from CDN — no auth header needed for these URLsconst res = awaitfetch(storyUrl);
if (!res.ok) {
// URL may have already expired if webhook processing was delayed
console.warn(`Story URL expired for story ${storyId}: ${res.status}`);
returnnull;
}
const contentType = res.headers.get('content-type'); // image/jpeg, video/mp4, etc.const bytes = Buffer.from(await res.arrayBuffer());
const ext = contentType.includes('video') ? 'mp4' : 'jpg';
const key = `stories/${storyId}.${ext}`;
// Store permanently in S3await s3.send(newPutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
Body: bytes,
ContentType: contentType,
}));
return`s3://${process.env.S3_BUCKET}/${key}`; // save THIS, not the CDN URL
}
Python + boto3
download_story.py
import os, boto3, requests
s3 = boto3.client("s3", region_name=os.environ["AWS_REGION"])
defdownload_and_store_story(story_url: str, story_id: str) -> str | None:
"""Download story media and store permanently. Return S3 path."""
res = requests.get(story_url)
if not res.ok:
# URL may have already expiredprint(f"Story URL expired: {res.status_code} for story {story_id}")
returnNone
content_type = res.headers.get("content-type", "image/jpeg")
ext = "mp4"if"video"in content_type else"jpg"
key = f"stories/{story_id}.{ext}"
s3.put_object(
Bucket=os.environ["S3_BUCKET"],
Key=key,
Body=res.content,
ContentType=content_type,
)
returnf"s3://{os.environ['S3_BUCKET']}/{key}"# save THIS, not CDN URL
Code complet de détection et de routage
Voici le gestionnaire de qualité production qui identifie correctement les deux types d'événements à partir du webhook brut et les achemine vers les gestionnaires appropriés :
Node.js — story event detection and routing
storyRouter.js
async functionrouteInstagramEvent(event) {
const igsid = event.sender.id;
// ── Story MENTION (user tagged you in THEIR story) ───────────────────────if (event.referral?.source === 'STORY_MENTION') {
const { story } = event.referral;
const storagePath = awaitdownloadAndStoreStory(story.url, story.id);
awaithandleStoryMention({ igsid, storyId: story.id, storagePath });
return;
}
// ── Story REPLY (user replied to YOUR story via DM) ─────────────────────if (event.message?.reply_to?.story) {
const { story } = event.message.reply_to;
const replyText = event.message.text || null; // may be null if emoji reactionconst attachments = event.message.attachments || []; // for emoji/media repliesconst storagePath = awaitdownloadAndStoreStory(story.url, story.id);
awaithandleStoryReply({
igsid,
storyId: story.id,
storagePath,
replyText,
attachments,
mid: event.message.mid,
});
return;
}
// ── Regular DM (no story context) ───────────────────────────────────────if (event.message?.text) {
awaithandleTextDM({ igsid, text: event.message.text, mid: event.message.mid });
return;
}
// ── Reaction, read, or other ────────────────────────────────────────────if (event.reaction) {
awaithandleReaction({ igsid, ...event.reaction });
}
}
Python — story event detection and routing
story_router.py
async defroute_instagram_event(event: dict) -> None:
igsid = event["sender"]["id"]
# ── Story MENTION (user tagged you in THEIR story) ───────────────────
referral = event.get("referral", {})
if referral.get("source") == "STORY_MENTION":
story = referral["story"]
storage_path = download_and_store_story(story["url"], story["id"])
awaithandle_story_mention(igsid=igsid, story_id=story["id"], storage_path=storage_path)
return# ── Story REPLY (user replied to YOUR story via DM) ──────────────────
message = event.get("message", {})
reply_to = message.get("reply_to", {})
if"story"in reply_to:
story = reply_to["story"]
storage_path = download_and_store_story(story["url"], story["id"])
awaithandle_story_reply(
igsid=igsid,
story_id=story["id"],
storage_path=storage_path,
reply_text=message.get("text"),
mid=message.get("mid"),
)
return# ── Regular DM ────────────────────────────────────────────────────────if message.get("text"):
awaithandle_text_dm(igsid=igsid, text=message["text"])
Pipeline complet de réponse automatique
Voici maintenant la partie utile — répondre automatiquement aux interactions de story. Le pattern est le même pour les deux types d'événements : détecter le contexte de la story, composer une réponse contextuelle et appeler la Send API d'Instagram dans la fenêtre de 24 heures.
Node.js — auto-respond to story replies and mentions
storyAutoReply.js
constGRAPH = 'https://graph.facebook.com/v21.0';
constIG_BIZ_ID = process.env.IG_BUSINESS_ID;
constTOKEN = process.env.IG_PAGE_TOKEN;
async functionsendDM(igsid, text) {
const res = awaitfetch(`${GRAPH}/${IG_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('DM send failed', await res.json());
return res.ok;
}
// Handle story REPLY — user replied to one of your storiesasync functionhandleStoryReply({ igsid, storyId, replyText, storagePath }) {
// Log for your team
console.log(`Story reply from ${igsid}: "${replyText}" to story ${storyId}`);
awaitdb.storyReplies.insert({ igsid, storyId, replyText, storagePath });
// Auto-respond within the 24h service windowconst autoReply = buildStoryReplyResponse(replyText);
awaitsendDM(igsid, autoReply);
}
// Handle story MENTION — user tagged you in their own storyasync functionhandleStoryMention({ igsid, storyId, storagePath }) {
console.log(`Story mention from ${igsid}, story ${storyId} saved: ${storagePath}`);
awaitdb.storyMentions.insert({ igsid, storyId, storagePath });
// Auto-reply to thank them for the mentionawaitsendDM(igsid,
"Hi! 👋 We just saw you mentioned us in your story — thank you so much! " +"We'd love to repost it. Would you be okay with that? Just reply 'Yes' if you agree. 🙏"
);
// Optional: alert your team via SlackawaitnotifySlack(`📸 New story mention from ${igsid} | Media: ${storagePath}`);
}
functionbuildStoryReplyResponse(replyText) {
const text = replyText?.toLowerCase() || '';
// Route to different responses based on intent signals in reply textif (text.includes('buy') || text.includes('price') || text.includes('cost')) {
return"Thanks for your interest! 💛 Check our link in bio for pricing. Want me to send you the direct link to this specific product?";
}
if (text.includes('where') || text.includes('ship') || text.includes('deliver')) {
return"We ship worldwide! 🌍 Orders typically arrive in 5-7 days. Want to place an order?";
}
if (text.includes('love') || text.includes('amazing') || text.includes('beautiful')) {
return"Aw, thank you so much! 😍 That really makes our day. Check out more on our page!";
}
// Default auto-reply for emoji reactions or unmatched textreturn"Thanks for watching our story! 🙏 Let us know if you have any questions — we're here to help!";
}
La fenêtre de 24 heures : quand vous pouvez et ne pouvez pas répondre
Les interactions de story ouvrent une fenêtre de messagerie de 24 heures — exactement comme les DMs ordinaires. Lorsqu'un utilisateur répond à votre story ou vous mentionne, la fenêtre s'ouvre immédiatement et reste ouverte pendant 24 heures après son dernier message. Pendant cette fenêtre, vous pouvez envoyer n'importe quel message librement. Après 24 heures, vous ne pouvez plus envoyer de message à cet utilisateur à moins qu'il n'envoie un autre message.
Réponse de story → la fenêtre s'ouvre immédiatement. L'utilisateur a initié le contact en répondant à votre story. Vous pouvez répondre tout de suite et poursuivre la conversation pendant 24 heures.
Mention de story → la fenêtre s'ouvre également. La mention est traitée comme un événement d'initiation. Vous pouvez envoyer un DM à la personne qui vous a mentionné dans les 24 heures suivant le déclenchement de l'événement referral.
Fenêtre expirée → impossible de répondre. Si vous traitez l'événement webhook plus de 24 heures après son déclenchement (traitement retardé, échec de retry), votre DM de réponse échouera avec une erreur de fenêtre Messenger.
Répondez automatiquement dans le gestionnaire webhook. L'approche la plus sûre : envoyez votre réponse automatique de manière synchrone dans le gestionnaire d'événement, immédiatement après le déclenchement du webhook. Cela garantit que vous êtes dans la fenêtre.
Vérifiez les timestamps avant de répondre. Si votre webhook a une logique de retry qui pourrait redélivrer un ancien événement, vérifiez toujours event.timestamp avant d'envoyer une réponse. Si l'événement a plus de 23 heures, sautez la réponse automatique — vous risqueriez de l'envoyer en dehors de la fenêtre (provoquant une erreur API) ou d'envoyer un doublon (si l'utilisateur en a déjà reçu une).
Cas d'usage et modèles de réponse
Cas d'usage des réponses de story
Détection d'intention d'achat : Des mots-clés comme « acheter », « prix », « où trouver » dans le texte de la réponse déclenchent un lien produit ou un code promo.
Triage du support : Des mots-clés comme « aide », « problème », « souci » acheminent l'utilisateur vers votre équipe de support via une alerte Slack et envoient un message d'attente.
Engagement et fidélisation : Les réactions emoji ou les réponses positives génériques reçoivent un accusé de réception chaleureux et un appel à l'action (suivre, s'abonner, consulter le lien en bio).
Sondage ou poll : Les stories qui demandent « Préférez-vous A ou B ? » peuvent capturer automatiquement la réponse texte et l'enregistrer dans un tableur.
Cas d'usage des mentions de story
Collecte d'UGC (User Generated Content) : Téléchargez et stockez immédiatement le média de la story. Demandez à l'utilisateur l'autorisation de republier. Constituez une bibliothèque de contenus clients authentiques.
Pipeline d'influenceurs : Signalez les mentions provenant de comptes au-dessus d'un certain seuil d'abonnés pour une revue manuelle par votre équipe marketing.
Campagne de remerciement : Envoyez automatiquement un code promo à toute personne qui vous mentionne dans sa story — récompense la promotion organique.
Création de contact CRM : Première mention d'un utilisateur qui n'est pas dans votre CRM ? Créez une fiche contact avec son IGSID.
SocialHook : événements de story pré-étiquetés au format normalisé
Lorsque vous connectez votre compte Instagram à SocialHook, les réponses et mentions de story arrivent déjà classifiées — vous n'analysez pas reply_to.story vs referral.source. Le champ de type d'événement vous indique exactement ce qui s'est passé :
SocialHook — story events already classified
// Story reply (user replied to YOUR story)
{
"platform": "instagram",
"event": "story.reply", // ← already classified, no parsing needed"from": "12345678901234", // IGSID"timestamp": 1747231892,
"story": {
"id": "17893310459840806",
"storage_url": "s3://bucket/stories/17893..."// already downloaded! no CDN expiry
},
"message": {
"type": "text",
"body": "This is amazing! Where can I buy it? 😍"
}
}
// Story mention (user tagged YOU in their story)
{
"platform": "instagram",
"event": "story.mention", // ← no referral.source parsing needed"from": "98765432109876",
"timestamp": 1747231892,
"story": {
"id": "17893310459898765",
"storage_url": "s3://bucket/stories/17893..."// already downloaded + stored
}
}
// Your handler becomes trivial:
app.post('/webhook', async (req, res) => {
res.sendStatus(200);
const { event, from, story, message } = req.body;
if (event === 'story.reply') awaithandleStoryReply({ igsid: from, ...story, replyText: message?.body });
if (event === 'story.mention') awaithandleStoryMention({ igsid: from, ...story });
});
FAQ
Questions fréquentes
Quelle est la différence entre une réponse de story et une mention de story sur Instagram ?
Une réponse de story : un utilisateur répond à l'une de VOS stories via DM. Arrive sous forme d'événement messages avec event.message.reply_to.story. Nécessite l'abonnement webhook messages. Une mention de story : un utilisateur identifie VOTRE compte dans SA propre story. Arrive sous forme d'événement referral avec event.referral.source === "STORY_MENTION". Nécessite l'abonnement webhook messaging_referrals — distinct de messages.
Pourquoi l'URL de la story dans le webhook expire-t-elle ?
Les URLs des médias de story Instagram sont hébergées sur le CDN de Meta et sont temporaires — elles expirent généralement en quelques heures. La bonne approche : téléchargez immédiatement les octets du média de la story lorsque le webhook se déclenche et stockez-les dans votre propre stockage permanent (S3, GCS, etc.). Sauvegardez le chemin de stockage dans votre base de données, et non l'URL CDN. L'ID de la story (story.id) est stable et peut être utilisé comme clé de référence.
Comment recevoir les mentions de story Instagram dans mon webhook ?
Vous devez vous abonner au champ webhook messaging_referrals — il est distinct du champ messages. Rendez-vous dans : Facebook App → Instagram → Webhooks → Modifier les abonnements → cochez messaging_referrals. Sans cet abonnement, les événements de mention de story disparaissent silencieusement — aucune erreur, aucun événement. Si vous n'avez pas eu cet abonnement, vous ne pouvez pas récupérer rétroactivement les mentions de story manquées.
Comment détecter si une réponse de story est un texte ou une réaction emoji ?
Vérifiez à la fois event.message.text et event.message.attachments. Réponse texte : event.message.text contient la chaîne. Réaction emoji/cœur en réponse : event.message.attachments contient un objet avec type: "like_heart" ou un type de sticker similaire. Réponse média (l'utilisateur a envoyé une photo/vidéo en réponse) : event.message.attachments avec type: "image" ou "video" et une URL de payload. Le texte comme les attachments ont également le contexte reply_to.story pour que vous sachiez qu'il s'agit de réponses de story.
Puis-je répondre automatiquement aux mentions de story dans la fenêtre de 24 heures ?
Oui. Lorsqu'un utilisateur vous mentionne dans sa story, une fenêtre de messagerie de 24 heures s'ouvre, vous permettant de lui envoyer un DM. Votre webhook reçoit l'événement referral — appelez la Send API d'Instagram avec l'IGSID de l'utilisateur comme destinataire dans les 24 heures. C'est ainsi que vous envoyez automatiquement des messages « merci pour la mention ! », des demandes d'autorisation de republier ou des codes promo. Après 24 heures sans interaction supplémentaire, la fenêtre se ferme et vous ne pouvez plus lui envoyer de DM.
Quelles permissions sont nécessaires pour les webhooks de réponse de story ?
Les mêmes que pour l'API Instagram Messaging en général : instagram_manage_messages, pages_messaging et pages_read_engagement. Pour les mentions de story spécifiquement, vous avez également besoin de l'abonnement webhook messaging_referrals (c'est un champ d'abonnement, pas une permission d'App — activez-le dans les paramètres webhook de votre App). Toutes les permissions nécessitent une App Review pour une utilisation en production au-delà des utilisateurs de test.
story.reply et story.mention déjà étiquetés pour vous.
SocialHook reçoit vos événements de story Instagram, télécharge le média avant que l'URL CDN n'expire, classifie les réponses de story vs les mentions, et livre le tout sous forme de JSON normalisé et propre. Aucune analyse de payload. Aucune condition de course CDN. 50 $/mois pour tous vos événements Instagram, Messenger et WhatsApp.
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.