Pasar al contenido principal
osky
Cómo manejar el nuevo código de error RCS 30046 en tu bot de WhatsApp con Twilio
Tema Twilio RCS + WhatsApp Bot

Cómo manejar el nuevo código de error RCS 30046 en tu bot de WhatsApp con Twilio

Si tienes un bot corriendo sobre Twilio — ya sea para WhatsApp, RCS, o ambos — y no actualizas tu manejo de errores antes del 10 de agosto de 2026, te vas a despertar con mensajes caídos, reintentos que no disparan, y clientes fantasma que nunca recibieron nada. No es una advertencia menor: es un cambio de comportamiento en producción que rompe lógica existente si tienes hardcodeado el código 30036.

Este tutorial te lleva de la mano para identificar dónde está el riesgo en tu código, actualizar los handlers correctamente, y dejar tu sistema listo para el nuevo código 30046 — el que Twilio usará a partir de esa fecha cuando un mensaje RCS no puede entregarse porque el dispositivo destino no está disponible o no soporta RCS.


Contexto: ¿qué cambió y por qué importa?

Twilio usa RCS (Rich Communication Services) como canal alterno o principal para algunos mercados. Cuando un mensaje RCS no puede entregarse — porque el dispositivo no tiene RCS habilitado, está apagado, o simplemente no es alcanzable — Twilio regresa un código de error en el webhook de estado del mensaje.

Hasta ahora ese código era 30036. A partir del 10 de agosto de 2026, ese mismo escenario regresa 30046.

El código 30036 no desaparece de golpe para todos los casos, pero el caso específico de "dispositivo no alcanzable en RCS" migra al 30046. Si tu bot escucha webhooks de estado y toma decisiones basado en ese código (como hacer fallback a SMS o WhatsApp), necesitas actualizar esa lógica.


Requisitos previos

  • Cuenta activa en Twilio con RCS habilitado (o en proceso de habilitarlo)
  • Bot con webhook de estado de mensajes configurado (statusCallback)
  • Node.js 20+ o Python 3.11+ (los ejemplos cubren ambos)
  • Acceso a logs de tu webhook actual para identificar si ya estás recibiendo 30036

Paso 1: Audita tu código actual

Busca en todo tu proyecto referencias al código 30036. En bash:

grep -r "30036" ./src

También busca variantes como strings, números en switches o comparaciones:

grep -rE "(30036|'30036'|\"30036\")" ./src

Si encuentras cero resultados y no tienes ningún manejo de errores por código numérico, tienes otro problema: no estás manejando errores de entrega en absoluto. Sigue leyendo de todas formas.


Paso 2: Entiende la estructura del webhook de estado

Cuando Twilio intenta entregar un mensaje y falla, manda un POST a tu statusCallback con un payload como este:

{
  "MessageSid": "SMxxxxxxxxxxxxxxxx",
  "MessageStatus": "failed",
  "ErrorCode": "30036",
  "ErrorMessage": "Message delivery failed: unreachable destination handset",
  "To": "whatsapp:+521234567890",
  "From": "whatsapp:+14155238886"
}

A partir del 10 de agosto, ese ErrorCode será "30046" para el caso de dispositivo RCS no alcanzable. El campo ErrorMessage también cambiará para reflejar el nuevo código.


Paso 3: Actualiza tu handler de webhook

En Node.js (Express):

// webhook/statusCallback.js

const TWILIO_RCS_UNREACHABLE_CODES = new Set(['30046', '30036']); // mantén ambos durante transición

app.post('/webhook/status', (req, res) => {
  const { MessageSid, MessageStatus, ErrorCode, To } = req.body;

  if (MessageStatus === 'failed') {
    if (TWILIO_RCS_UNREACHABLE_CODES.has(ErrorCode)) {
      console.warn(`[RCS_UNREACHABLE] SID: ${MessageSid} | To: ${To} | Code: ${ErrorCode}`);
      // Lógica de fallback: intentar por WhatsApp o SMS
      handleRCSFallback({ messageSid: MessageSid, destination: To });
    } else {
      console.error(`[MSG_FAILED] SID: ${MessageSid} | Code: ${ErrorCode}`);
      // Otros errores: loggear, alertar, reintentar según política
    }
  }

  res.sendStatus(200);
});

En Python (FastAPI):

# routes/status_callback.py
from fastapi import APIRouter, Request
import logging

router = APIRouter()

TWILIO_RCS_UNREACHABLE_CODES = {"30046", "30036"}  # ambos durante transición

@router.post("/webhook/status")
async def status_callback(request: Request):
    form = await request.form()
    message_sid = form.get("MessageSid")
    status = form.get("MessageStatus")
    error_code = form.get("ErrorCode")
    destination = form.get("To")

    if status == "failed":
        if error_code in TWILIO_RCS_UNREACHABLE_CODES:
            logging.warning(f"[RCS_UNREACHABLE] SID: {message_sid} | To: {destination} | Code: {error_code}")
            await handle_rcs_fallback(message_sid, destination)
        else:
            logging.error(f"[MSG_FAILED] SID: {message_sid} | Code: {error_code}")

    return {"status": "ok"}

Nota importante: Mantén ambos códigos (30036 y 30046) en tu set de códigos manejados durante el periodo de transición. Twilio no garantiza que el switch sea instantáneo para todas las cuentas exactamente el 10 de agosto — puede haber un margen de horas. Elimina el 30036 del set de RCS unreachable un par de semanas después de confirmado el cambio en tus logs.


Paso 4: Implementa la lógica de fallback

Si recibes un 30046, lo más común es intentar el mismo mensaje por un canal alterno. Aquí un ejemplo con fallback a WhatsApp:

// services/fallback.js
const twilio = require('twilio');
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

async function handleRCSFallback({ messageSid, destination }) {
  // Recupera el mensaje original de tu DB usando el SID
  const originalMessage = await db.messages.findOne({ twilioSid: messageSid });
  if (!originalMessage) {
    console.error(`No se encontró mensaje original para SID: ${messageSid}`);
    return;
  }

  // Intenta por WhatsApp
  try {
    const fallbackMsg = await client.messages.create({
      from: 'whatsapp:+14155238886',
      to: destination.startsWith('whatsapp:') ? destination : `whatsapp:${destination}`,
      body: originalMessage.body,
    });
    console.log(`[FALLBACK_OK] Nuevo SID: ${fallbackMsg.sid}`);
    await db.messages.update({ twilioSid: messageSid }, { fallbackSid: fallbackMsg.sid, fallbackChannel: 'whatsapp' });
  } catch (err) {
    console.error(`[FALLBACK_FAIL] ${err.message}`);
    // Aquí puedes encolar para SMS o notificar al equipo
  }
}

Paso 5: Actualiza tus alertas y dashboards

Si tienes alertas en Datadog, Grafana, Sentry, o cualquier herramienta de observabilidad configuradas con el código 30036 como filtro, actualízalas ahora. Una alerta que filtra por ErrorCode == 30036 dejará de dispararse para el escenario de dispositivo no alcanzable después del cambio.

Crea una alerta nueva para 30046 o, mejor, una que cubra rangos de errores Twilio relevantes para tu operación.


Paso 6: Prueba en staging antes del 10 de agosto

Twilio no ofrece un simulador nativo para forzar un error 30046 en sandbox, pero puedes:

  1. Enviar a un número que no exista o que tengas confirmado sin RCS habilitado
  2. Mockear el webhook de estado en tu entorno de pruebas con un payload que incluya "ErrorCode": "30046"
  3. Usar una herramienta como ngrok + un script de prueba para disparar tu handler manualmente
curl -X POST https://tu-ngrok.ngrok.io/webhook/status \
  -d "MessageSid=SMtest123" \
  -d "MessageStatus=failed" \
  -d "ErrorCode=30046" \
  -d "ErrorMessage=Message delivery failed: unreachable destination handset" \
  -d "To=whatsapp:+521234567890" \
  -d "From=whatsapp:+14155238886"

Confirma que tu handler responde con 200 y que la lógica de fallback se ejecuta correctamente.


Advertencias de produccion

  • No borres el manejo de 30036 de inmediato. Hay otros escenarios donde ese código puede seguir apareciendo. Revisa la documentación de Twilio para los casos que permanecen bajo 30036.
  • Idempotencia en el fallback. Tu función de fallback debe verificar si ya se envió un fallback para ese SID antes de crear uno nuevo. Los webhooks de Twilio pueden llegar duplicados.
  • Rate limits. Si tienes un volumen alto de mensajes RCS fallidos al mismo tiempo, tu lógica de fallback puede golpear los rate limits de Twilio. Implementa una cola con backoff exponencial.
  • Logging estructurado. Loggea el ErrorCode como campo separado, no concatenado en un string. Eso te permite filtrar y agrupar en tu sistema de observabilidad sin regex.

Resumen del cambio

Escenario Código anterior Código nuevo (desde 10 ago 2026)
Dispositivo RCS no alcanzable 30036 30046
Otros errores de entrega varios sin cambio

Actualiza, prueba, y duerme tranquilo. El cambio es pequeño pero el impacto en producción puede ser grande si lo ignoras.