developers
EN Crear cuenta

Guías

Migrar desde Twilio, 360dialog, YCloud o Kapso

Si hoy mandas WhatsApp con Twilio, 360dialog, YCloud o Kapso, pasar a heysteff es cambiar una llamada: todo sale por el mismo POST /v1/messages, y con ese mismo endpoint también llegas a Instagram, Messenger, SMS y correo. Esta guía junta las equivalencias de endpoints, webhooks y conceptos, y los pasos para cambiarte sin cortar el servicio.

Los datos de cada proveedor salen de su documentación pública (revisada en noviembre de 2026). Donde no pudimos confirmar un detalle, lo decimos de forma general: revisa la documentación de tu proveedor antes de borrar código.

Lo que cambia#

Desde la Cloud API de Meta directa#

Si hoy llamas a graph.facebook.com con tu propio token de Meta, no tienes que reescribir nada: cambia la URL base a https://app.heysteff.com/api/meta/whatsapp/v21.0 (sirve cualquier versión) y el token por tu API key de heysteff (Authorization: Bearer sk_live_…). El phone_number_id sigue siendo el mismo. Para tu receptor de webhooks, crea el webhook con format: "meta": llega el mismo payload de Meta, firmado con X-Hub-Signature-256 y el secreto del webhook. Detalle en Compatible con Meta. Si vienes de un proveedor que expone la Cloud API (360dialog, Kapso), también puedes usar esa misma URL en vez de pasar a /v1.

Equivalencias de endpoints#

OperaciónheysteffTwilio360dialogYCloudKapso
Base y autenticaciónhttps://app.heysteff.com/api/v1
Authorization: Bearer
https://api.twilio.com/2010-04-01
Basic auth
https://waba-v2.360dialog.io
D360-API-KEY
https://api.ycloud.com/v2
X-API-Key
https://api.kapso.ai/meta/whatsapp/v24.0
X-API-Key
Mandar un mensajePOST /v1/messagesPOST /Accounts/{AccountSid}/Messages.json (formulario)POST /messagesPOST /whatsapp/messages o /whatsapp/messages/sendDirectlyPOST /{phone_number_id}/messages
Mandar una plantillaEl mismo POST /v1/messages con content_type: "template"ContentSid y ContentVariables en el mismo POSTEl mismo POST /messages con type: "template"El mismo POST con type: "template"El mismo POST con type: "template"
Listar plantillasGET /v1/templatesGET https://content.twilio.com/v1/ContentGET /message_templatesRevisa su documentación (crear: POST /whatsapp/templates)GET /{business_account_id}/message_templates
Subir un archivoPOST /v1/media (multipart, campo file), o una URL https pública en media.urlURL pública en MediaUrlPOST /media (multipart)POST /whatsapp/media/{phoneNumber}/uploadPOST /{phone_number_id}/media
ContactosGET /v1/contacts, POST /v1/contactsLo manejas en tu sistemaRevisa su documentaciónPOST /contact/contactsGET /{phone_number_id}/contacts
Conversaciones e historialGET /v1/conversations, GET /v1/conversations/{id}/messagesRevisa su documentaciónRevisa su documentaciónRevisa su documentaciónRevisa su documentación

Antes y después, con un texto:

Twilio (antes)
curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Messages.json" \
  -u "$TWILIO_API_KEY:$TWILIO_API_SECRET" \
  --data-urlencode "From=whatsapp:+14155550100" \
  --data-urlencode "To=whatsapp:+56912345678" \
  --data-urlencode "Body=Hola Ana, tu pedido ya va en camino"
heysteff (después)
curl https://app.heysteff.com/api/v1/messages \
  -H "Authorization: Bearer $HEYSTEFF_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1042-en-camino" \
  -d '{ "channel": "whatsapp_cloud", "to": "+56912345678", "content": "Hola Ana, tu pedido ya va en camino" }'

Y una plantilla en el formato de la Cloud API (360dialog, YCloud y Kapso usan este cuerpo o uno muy parecido):

Cloud API (antes)
{
  "messaging_product": "whatsapp",
  "to": "56912345678",
  "type": "template",
  "template": {
    "name": "pedido_despachado",
    "language": { "code": "es" },
    "components": [{ "type": "body", "parameters": [
      { "type": "text", "text": "Ana" }, { "type": "text", "text": "#1042" }
    ] }]
  }
}
heysteff (después)
{
  "channel": "whatsapp_cloud",
  "to": "+56912345678",
  "content_type": "template",
  "template": { "name": "pedido_despachado", "language": "es", "body_params": ["Ana", "#1042"] }
}

Si tu plantilla lleva encabezado con imagen o botones con variables, manda template.components con la forma de la Graph API de Meta: heysteff la respeta y gana sobre body_params. Más en Tipos de mensaje.

Webhooks y firma#

heysteff manda un solo formato para todos los canales: un POST JSON con el sobre { id, tenant_id, event, ts, data }. El mensaje entrante trae en data todo lo que necesitas para contestar (channel, external_user_id, channel_connection_id).

heysteffTwilio360dialogYCloudKapso
Dónde se configuraConsola, sección WebhooksConsola de Twilio (entrantes) y StatusCallback por mensajePOST /v1/configs/webhook o POST /waba_webhookPOST /v2/webhookEndpointsPOST /platform/v1/whatsapp/phone_numbers/{id}/webhooks
Mensaje entrantemessage:receivedPOST de formulario a tu URL de entradaObjeto messages del payload de Metawhatsapp.inbound_message.receivedwhatsapp.message.received
Estado de un mensaje enviadomessage:sent y message.failedMessageStatus en StatusCallbackObjeto statuseswhatsapp.message.updatedwhatsapp.message.sent, .delivered, .read, .failed
Header de la firmaX-Heysteff-Signature (v1= + hex) y X-Heysteff-TimestampX-Twilio-SignatureNo documenta una firma: autentica con headers propios que defines al crear el webhookYCloud-Signature (t=…,s=…)X-Webhook-Signature
Qué se firmaHMAC-SHA256 de ${timestamp}.${cuerpo crudo}HMAC-SHA1 de la URL más los parámetros del POST, con tu auth tokenNo aplicaHMAC-SHA256 de t + "." + cuerpo crudoHMAC-SHA256 del cuerpo crudo
Reintentos y duplicadosDeduplica por X-Heysteff-Delivery-Id, que se repite en cada reintentoRevisa su documentaciónMeta reintenta si no respondes 200Reintenta con espera crecienteDeduplica por X-Idempotency-Key

Si vienes de YCloud, la firma es casi igual: cambia el header y el prefijo v1=. Si vienes de Kapso, suma el timestamp al string firmado. Si vienes de Twilio, deja de firmar la URL: heysteff firma el cuerpo crudo.

Node.js
import crypto from 'node:crypto';

// rawBody: el cuerpo tal como llegó, sin parsear.
function isFromHeysteff(rawBody, timestamp, signature, secret) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = 'v1=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

El SDK lo hace por ti con verifyWebhook, y heysteff webhooks trigger de la CLI te manda un aviso firmado a localhost para probar. Detalle en Webhooks.

Conceptos#

ConceptoEn heysteffCómo se llamaba antes
Número o cuentaUn canal conectado (channel_connection_id). Un mismo espacio tiene números de WhatsApp, cuentas de Instagram y Messenger, SMS y correo.Twilio: el From (whatsapp:+…) o un Messaging Service. 360dialog: la API key de cada número. YCloud: el from. Kapso: el phone_number_id en la URL.
PlantillaSe manda por nombre e idioma (template.name, template.language) con body_params en orden. GET /v1/templates lista las aprobadas.Twilio: un ContentSid de su Content API. En los proveedores con formato Cloud API, el mismo nombre e idioma de Meta.
Ventana de 24 horasIgual que en todos: WhatsApp solo deja mandar texto libre dentro de las 24 horas siguientes al último mensaje del cliente; fuera de ella, una plantilla. Fuera de la ventana la API responde con un error claro en vez de aceptar un mensaje que no se va a entregar.Es una regla de WhatsApp, no del proveedor: no cambia.
SandboxUna key sk_test_ y el número compartido de heysteff: unes hasta 5 teléfonos con un código desde la consola. Ver Sandbox.Twilio: número compartido y join <código>. 360dialog: mensaje START a su número de sandbox. Kapso: código de 6 caracteres a su número de sandbox.
Bandeja y personasCada conversación también aparece en la bandeja de la web y de la app móvil. POST /v1/conversations/{id}/escalate se la pasa a una persona.Depende del proveedor.

Pasos para cambiarte#

  1. Crea tu cuenta y tu API key. Crea una cuenta developer y corre curl -fsSL https://developers.heysteff.com/install.sh | bash y después heysteff setup: te lleva a la consola, guarda la key y manda un mensaje de prueba.
  2. Prueba en el sandbox. Con una key sk_test_ cambia tus llamadas a POST /v1/messages sin tocar tu número actual.
  3. Mueve tu receptor de webhooks. Crea el webhook en la consola, guarda el secreto y cambia la verificación de la firma (ver arriba). Mientras migras, tu servidor puede aceptar los dos formatos.
  4. Conecta un número. En la consola, sección Números, conecta tu número con la ventana de alta de Meta o usa un número de heysteff. Si tu número hoy está en otro proveedor, sigue el proceso de Meta para llevarlo a otra cuenta o proveedor, y revisa con tu proveedor actual qué pasa con sus plantillas.
  5. Revisa tus plantillas. GET /v1/templates muestra las aprobadas del número conectado. Si venías de Twilio, reemplaza cada ContentSid por el nombre y el idioma de la plantilla.
  6. Cambia a sk_live_ y apaga el proveedor anterior cuando veas pasar tus mensajes en la bandeja y en el registro de entregas.

¿Usas Claude Code, Cursor o Codex? Instala la skill de heysteff (ver Agentes de código) y pídele que migre tu integración con esta guía.