Webhooks de respuestas a stories de Instagram — esquema del payload mostrando reply_to.story, advertencia de expiración de la URL del CDN, messaging_referrals para menciones en stories, pipeline de auto-respuesta
En esta guía: Respuesta a story vs mención en story — la distinción que importa · Esquemas exactos del payload de webhook para ambos · La suscripción messaging_referrals · Expiración de la URL del CDN y cómo manejarla · La ventana de 24 horas para auto-respuestas · Código completo del pipeline de auto-respuesta (Node.js + Python) · Casos de uso y plantillas · Formato normalizado de SocialHook

Dos eventos distintos: respuesta a story vs mención en story

Lo primero que hay que entender: "interacciones con stories de Instagram" abarca dos eventos de webhook completamente diferentes, con estructuras de payload distintas, suscripciones de webhook distintas y acciones desencadenadas distintas. Confundirlos es la causa número 1 de bugs en los sistemas de automatización de stories.

Respuesta a story
El usuario responde a TU story
"Publicas una story. Un seguidor desliza hacia arriba y envía 'This is amazing!' como respuesta por DM a tu story."
Suscripción: messages
Tipo de evento: evento de mensaje
Detección: event.message.reply_to.story
Ruta del payload: event.message.text (su respuesta)
URL de la story: event.message.reply_to.story.url
Mención en story
El usuario TE menciona en SU story
"Un cliente publica una story mostrando tu producto y etiqueta tu cuenta de negocio con @yourbrand."
Suscripción: messaging_referrals
Tipo de evento: evento de referral
Detección: event.referral.source === 'STORY_MENTION'
Ruta del payload: event.referral.story.url
ID de la story: event.referral.story.id

Suscripciones de webhook: qué campos necesitas

Este es el detalle de configuración que más se pasa por alto — cada tipo de evento de story requiere su propia suscripción de campo de webhook. Ambas deben estar habilitadas en tu Facebook App → Instagram → Webhooks → Edit Subscriptions.

CampoRequerido paraQué pierdes sin él
messages Requerido Todos los DMs, incluidas las respuestas a stories. Sin esto, no recibes ningún evento de mensaje — ni DMs de texto, ni respuestas a stories, ni mensajes multimedia. Es la suscripción base.
messaging_referrals Requerido Menciones en stories (cuando los usuarios te etiquetan en sus propias stories) y entradas desde anuncios Click-to-DM. Sin esto, las menciones en stories desaparecen silenciosamente — sin error, sin evento, simplemente nada.
messaging_optins Opcional Eventos de opt-in cuando los usuarios pulsan Permitir en un widget de opt-in de notificaciones recurrentes. No es necesario para interacciones con stories.
Suscríbete a ambas ahora mismo. Si actualmente solo tienes suscrito messages, ya has perdido todas las menciones en stories que han llegado a tu cuenta. No hay forma de recuperarlas de manera retroactiva — los eventos se dispararon y se perdieron. Añade messaging_referrals en la configuración de tu App inmediatamente después de leer esta sección.

Respuesta a story: payload exacto del webhook

Cuando un usuario responde a tu story de Instagram por DM, el evento llega como un evento de webhook messages estándar — pero con un objeto adicional reply_to anidado dentro del mensaje. Así es como distingues una respuesta a story de un DM normal.

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

Mención en story: payload exacto del webhook

Cuando un usuario etiqueta tu cuenta en su propia story, el evento llega como un evento referral — completamente separado del objeto message. La estructura es claramente distinta, y por eso se confunden a menudo.

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

El problema de la expiración de la URL del CDN — descarga inmediatamente

Tanto el payload de respuesta a story como el de mención en story incluyen una URL de story — un enlace del CDN que apunta al medio de la story (imagen o vídeo). Esta URL es temporal. Normalmente expira en unas pocas horas tras la activación del webhook. Si guardas la URL e intentas cargarla más tarde — en tu base de datos, en tu CRM, en un mensaje de Slack — obtienes un 403 o 404.

El único enfoque correcto: descargar los bytes del medio de la story inmediatamente cuando se dispara el webhook, antes de confirmar o terminar el procesamiento del evento.

Node.js — download story media before it expires
downloadStory.js
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); const s3 = new S3Client({ region: process.env.AWS_REGION }); async function downloadAndStoreStory(storyUrl, storyId) { // Download story media from CDN — no auth header needed for these URLs const res = await fetch(storyUrl); if (!res.ok) { // URL may have already expired if webhook processing was delayed console.warn(`Story URL expired for story ${storyId}: ${res.status}`); return null; } 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 S3 await s3.send(new PutObjectCommand({ 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"]) def download_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 expired print(f"Story URL expired: {res.status_code} for story {story_id}") return None 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, ) return f"s3://{os.environ['S3_BUCKET']}/{key}" # save THIS, not CDN URL

Código completo de detección y enrutamiento

Este es el handler de calidad de producción que identifica correctamente ambos tipos de evento a partir del webhook crudo y los enruta a los handlers correspondientes:

Node.js — story event detection and routing
storyRouter.js
async function routeInstagramEvent(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 = await downloadAndStoreStory(story.url, story.id); await handleStoryMention({ 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 reaction const attachments = event.message.attachments || []; // for emoji/media replies const storagePath = await downloadAndStoreStory(story.url, story.id); await handleStoryReply({ igsid, storyId: story.id, storagePath, replyText, attachments, mid: event.message.mid, }); return; } // ── Regular DM (no story context) ─────────────────────────────────────── if (event.message?.text) { await handleTextDM({ igsid, text: event.message.text, mid: event.message.mid }); return; } // ── Reaction, read, or other ──────────────────────────────────────────── if (event.reaction) { await handleReaction({ igsid, ...event.reaction }); } }
Python — story event detection and routing
story_router.py
async def route_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"]) await handle_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"]) await handle_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"): await handle_text_dm(igsid=igsid, text=message["text"])

Pipeline completo de auto-respuesta

Ahora la parte útil — responder automáticamente a las interacciones con stories. El patrón es el mismo para ambos tipos de evento: detectar el contexto de la story, componer una respuesta contextual y llamar a la Send API de Instagram dentro de la ventana de 24 horas.

Node.js — auto-respond to story replies and mentions
storyAutoReply.js
const GRAPH = 'https://graph.facebook.com/v21.0'; const IG_BIZ_ID = process.env.IG_BUSINESS_ID; const TOKEN = process.env.IG_PAGE_TOKEN; async function sendDM(igsid, text) { const res = await fetch(`${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 stories async function handleStoryReply({ igsid, storyId, replyText, storagePath }) { // Log for your team console.log(`Story reply from ${igsid}: "${replyText}" to story ${storyId}`); await db.storyReplies.insert({ igsid, storyId, replyText, storagePath }); // Auto-respond within the 24h service window const autoReply = buildStoryReplyResponse(replyText); await sendDM(igsid, autoReply); } // Handle story MENTION — user tagged you in their own story async function handleStoryMention({ igsid, storyId, storagePath }) { console.log(`Story mention from ${igsid}, story ${storyId} saved: ${storagePath}`); await db.storyMentions.insert({ igsid, storyId, storagePath }); // Auto-reply to thank them for the mention await sendDM(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 Slack await notifySlack(`📸 New story mention from ${igsid} | Media: ${storagePath}`); } function buildStoryReplyResponse(replyText) { const text = replyText?.toLowerCase() || ''; // Route to different responses based on intent signals in reply text if (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 text return "Thanks for watching our story! 🙏 Let us know if you have any questions — we're here to help!"; }

La ventana de 24 horas: cuándo puedes responder y cuándo no

Las interacciones con stories abren una ventana de mensajería de 24 horas — exactamente igual que los DMs normales. Cuando un usuario responde a tu story o te menciona, la ventana se abre de inmediato y permanece abierta durante 24 horas tras su último mensaje. Dentro de esta ventana puedes enviar cualquier mensaje libremente. Después de 24 horas, no puedes volver a enviar mensajes a ese usuario a menos que envíe otro mensaje.

  • Respuesta a story → la ventana se abre de inmediato. El usuario inició el contacto al responder a tu story. Puedes responder enseguida y continuar la conversación durante 24 horas.
  • Mención en story → la ventana también se abre. La mención se trata como un evento de iniciación. Puedes enviar un DM a la persona que te mencionó dentro de las 24 horas posteriores al disparo del evento de referral.
  • Ventana expirada → no puedes responder. Si procesas el evento de webhook más de 24 horas después de su disparo (procesamiento retrasado, fallo de reintento), tu DM de respuesta fallará con un error de ventana de Messenger.
  • Auto-respuesta dentro del handler del webhook. El enfoque más seguro: envía tu auto-respuesta sincrónicamente dentro del handler del evento, inmediatamente después de que se dispare el webhook. Esto garantiza que estás dentro de la ventana.
Comprueba los timestamps antes de responder. Si tu webhook tiene lógica de reintentos que podría volver a entregar un evento antiguo, comprueba siempre event.timestamp antes de enviar una respuesta. Si el evento tiene más de 23 horas, omite la auto-respuesta — correrías el riesgo de enviarla fuera de la ventana (provocando un error de API) o de enviar un duplicado (si el usuario ya recibió uno).

Casos de uso y plantillas de respuesta

Casos de uso de respuesta a story

  • Detección de intención de compra: Palabras clave como "buy", "price", "where to get" en el texto de la respuesta activan un enlace de producto o un código de descuento.
  • Clasificación de soporte: Palabras clave como "help", "problem", "issue" enrutan al usuario a tu equipo de soporte mediante una alerta de Slack y envían un mensaje de espera.
  • Cultivo de engagement: Las reacciones con emoji o las respuestas positivas genéricas reciben un agradecimiento cálido y una llamada a la acción (seguir, suscribirse, revisar el enlace en bio).
  • Encuesta o sondeo: Las stories que preguntan "Would you prefer A or B?" pueden capturar automáticamente la respuesta de texto y registrarla en una hoja de cálculo.

Casos de uso de mención en story

  • Recolección de UGC (contenido generado por el usuario): Descarga y almacena el medio de la story de inmediato. Pide permiso al usuario para repostearlo. Construye una biblioteca de contenido auténtico de clientes.
  • Pipeline de influencers: Marca las menciones de cuentas con un número de seguidores por encima de un umbral para revisión manual por tu equipo de marketing.
  • Campaña de agradecimiento: Envía automáticamente un código de descuento a cualquiera que te mencione en su story — recompensa la promoción orgánica.
  • Creación de contactos en el CRM: ¿Primera mención de un usuario que no está en tu CRM? Crea un registro de contacto con su IGSID.

SocialHook: eventos de story pre-etiquetados en formato normalizado

Cuando conectas tu cuenta de Instagram a SocialHook, las respuestas a stories y las menciones en stories llegan ya clasificadas — no tienes que parsear reply_to.story vs referral.source. El campo de tipo de evento te dice exactamente qué ha ocurrido:

Preguntas frecuentes

¿Cuál es la diferencia entre una respuesta a una story de Instagram y una mención en una story?
Una respuesta a story: un usuario responde por DM a una de TUS stories. Llega como un evento messages con event.message.reply_to.story. Requiere la suscripción de webhook messages. Una mención en story: un usuario etiqueta TU cuenta en SU propia story. Llega como un evento referral con event.referral.source === "STORY_MENTION". Requiere la suscripción de webhook messaging_referrals — separada de messages.
¿Por qué expira la URL de la story en el webhook?
Las URLs de los medios de las stories de Instagram están alojadas en el CDN de Meta y son temporales — normalmente expiran en unas pocas horas. El enfoque correcto: descarga los bytes del medio de la story inmediatamente cuando se dispara el webhook y guárdalos en tu propio almacenamiento permanente (S3, GCS, etc.). Guarda la ruta de almacenamiento en tu base de datos, no la URL del CDN. El ID de la story (story.id) es estable y puede usarse como clave de referencia.
¿Cómo recibo menciones en stories de Instagram en mi webhook?
Debes suscribirte al campo de webhook messaging_referrals — está separado del campo messages. Ve a: Facebook App → Instagram → Webhooks → Edit Subscriptions → marca messaging_referrals. Sin esta suscripción, los eventos de mención en story desaparecen silenciosamente — sin error, sin evento. Si te ha faltado esta suscripción, no puedes recuperar de forma retroactiva las menciones en stories perdidas.
¿Cómo detecto si una respuesta a story es una respuesta de texto o una reacción con emoji?
Comprueba tanto event.message.text como event.message.attachments. Respuesta de texto: event.message.text contiene la cadena. Reacción con emoji/corazón como respuesta: event.message.attachments contiene un objeto con type: "like_heart" o un tipo de sticker similar. Respuesta multimedia (el usuario envía una foto/vídeo como respuesta): event.message.attachments con type: "image" o "video" y una URL en el payload. Tanto el texto como los attachments también tienen el contexto reply_to.story, así que sabes que son respuestas a stories.
¿Puedo auto-responder a menciones en stories dentro de la ventana de 24 horas?
Sí. Cuando un usuario te menciona en su story, se abre una ventana de mensajería de 24 horas que te permite enviarle un DM. Tu webhook recibe el evento de referral — llama a la Send API de Instagram con el IGSID del usuario como destinatario dentro de las 24 horas. Así es como envías mensajes de "¡gracias por la mención!", solicitudes de permiso para repostear o códigos de descuento de forma automática. Después de 24 horas sin más interacción, la ventana se cierra y no puedes enviarle DMs.
¿Qué permisos necesito para los webhooks de respuesta a story?
Los mismos que para la Instagram Messaging API en general: instagram_manage_messages, pages_messaging y pages_read_engagement. Para las menciones en stories específicamente, también necesitas la suscripción de webhook messaging_referrals (es un campo de suscripción, no un permiso de App — habilítalo en la configuración de webhooks de tu App). Todos los permisos requieren App Review para uso en producción más allá de los usuarios de prueba.

story.reply y story.mention
ya etiquetados para ti.

SocialHook recibe tus eventos de stories de Instagram, descarga el medio antes de que expire la URL del CDN, clasifica las respuestas a stories vs las menciones, y entrega todo como JSON normalizado y limpio. Sin parseo de payload. Sin race conditions del CDN. 50 $/mes para todos tus eventos de Instagram, Messenger y WhatsApp.

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