Inicio rápido
La API de heysteff
Manda mensajes por WhatsApp, Instagram, Messenger, SMS y Gmail, sincroniza contactos, dispara flujos y recibe cada respuesta en tu servidor con webhooks firmados. Una API REST, una API key y JSON.
- Base
- https://app.heysteff.com/api/v1
- Auth
- Authorization: Bearer
- Formato
- JSON
- Especificación
- OpenAPI 3.1
En tres pasos vas a mandar un WhatsApp desde tu código y a recibir la respuesta del cliente en tu servidor.
Crea una API key#
¿Todavía no tienes cuenta? Crea una cuenta de developer: la primera pantalla crea tu API key y te deja este ejemplo listo para copiar.
Si ya tienes un workspace, entra a Configuración → Desarrolladores → API Keys y toca Nueva API key. Ponle un nombre y elige los scopes que va a usar tu integración; para esta guía bastan messages:write y conversations:read.
El secreto (empieza con sk_live_) se muestra una sola vez. Guárdalo en una variable de entorno:
export HEYSTEFF_API_KEY="sk_live_..."Una API key da acceso a tu workspace. No la pongas en código del navegador, en una app móvil ni en un repositorio. Si se filtra, revócala desde el mismo panel.
Envía tu primer mensaje#
Necesitas un canal conectado en el workspace (por ejemplo, WhatsApp). Sin channel_connection_id, el mensaje sale por el canal predeterminado para la API.
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 #1042 ya va en camino."
}'const res = await fetch('https://app.heysteff.com/api/v1/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HEYSTEFF_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'pedido-1042-en-camino',
},
body: JSON.stringify({
channel: 'whatsapp_cloud',
to: '+56912345678',
content: 'Hola Ana, tu pedido #1042 ya va en camino.',
}),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const { data } = await res.json();
console.log(data.id, data.status);import os, requests
res = requests.post(
"https://app.heysteff.com/api/v1/messages",
headers={
"Authorization": f"Bearer {os.environ['HEYSTEFF_API_KEY']}",
"Idempotency-Key": "pedido-1042-en-camino",
},
json={
"channel": "whatsapp_cloud",
"to": "+56912345678",
"content": "Hola Ana, tu pedido #1042 ya va en camino.",
},
timeout=15,
)
res.raise_for_status()
print(res.json()["data"]["id"])La respuesta llega con el mensaje encolado. El envío al proveedor ocurre enseguida, en segundo plano:
{
"data": {
"id": "0b6e1c2a-5f3d-4e8a-9c1b-7d2e3f4a5b6c",
"conversation_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"status": "pending",
"content": "Hola Ana, tu pedido #1042 ya va en camino.",
"content_type": "text",
"created_at": "2026-10-31T14:02:11.482Z"
}
}El header Idempotency-Key es opcional en este endpoint, pero conviene mandarlo: si tu código reintenta por un corte de red, el cliente recibe el mensaje una sola vez. Más detalle en Errores y límites.
Fuera de la ventana de 24 horas#
WhatsApp solo deja mandar texto libre dentro de las 24 horas siguientes al último mensaje del cliente. Fuera de esa ventana usa una plantilla aprobada por Meta:
curl https://app.heysteff.com/api/v1/messages \
-H "Authorization: Bearer $HEYSTEFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp_cloud",
"to": "+56912345678",
"content_type": "template",
"template": {
"name": "pedido_en_camino",
"language": "es",
"body_params": ["Ana", "#1042"]
}
}'Por SMS#
Con un número SMS de heysteff conectado al workspace, el mismo endpoint manda SMS. Usa "channel": "sms" y el teléfono en formato internacional:
curl https://app.heysteff.com/api/v1/messages \
-H "Authorization: Bearer $HEYSTEFF_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pedido-1042-sms" \
-d '{
"channel": "sms",
"to": "+56912345678",
"content": "Tu pedido #1042 va en camino. Responde STOP para dejar de recibir SMS."
}'- Solo texto:
content_typetiene que sertext, con hasta 1.600 caracteres. Un número que no está en formato internacional, otro tipo de contenido o un texto más largo responden422 invalid_sms_message. - Si pasas un
channel_connection_id, tiene que ser el de un número SMS; si no, la respuesta es422 sms_sender_mismatch. - Se cobra por segmento: 160 caracteres sin tildes ni emojis (153 por parte si el mensaje se divide), o 70 si lleva alguno (67 por parte). Cada mensaje admite hasta 3 segmentos y el workspace, 1.000 al día; un mensaje que se pasa queda fallido y no se cobra.
- Quien responde STOP, BAJA o CANCELAR deja de recibir SMS de tu número. Las respuestas llegan a tu webhook con
"channel_type": "sms".
Recibe las respuestas con un webhook#
En Configuración → Desarrolladores → Webhooks salientes crea un webhook con la URL de tu servidor y el evento message:received. Copia el secreto que te muestra: lo vas a usar para verificar cada entrega.
Cada vez que un cliente escribe, heysteff hace un POST a tu URL con este cuerpo:
{
"id": "5d1f0c7e-8a2b-4c3d-9e4f-0a1b2c3d4e5f",
"tenant_id": "2c7e9b1a-3d4f-4a5b-8c6d-7e8f9a0b1c2d",
"event": "message:received",
"ts": "2026-10-31T14:03:52.104Z",
"data": {
"conversation_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"message_id": "4e3d2c1b-0a9f-4e8d-7c6b-5a4f3e2d1c0b",
"channel_type": "whatsapp_cloud",
"content": "Gracias, ¿a qué hora llega?",
"content_type": "text",
"contact_id": "7f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2918",
"media_url": null,
"filename": null
}
}Antes de confiar en el contenido, verifica la firma del header X-Heysteff-Signature. La guía de Webhooks tiene el código para Node.js y Python, y un vector de prueba para validar tu implementación.
Empieza con una plantilla#
Si prefieres partir de un proyecto que ya funciona, crea uno con un comando. Copia la plantilla, deja tu key en .env.local (la toma de la CLI si ya hiciste heysteff login) e instala las dependencias.
npm create heysteff@latest- Bandeja omnicanalApp Next.js con WhatsApp, Instagram, Messenger, SMS y correo en una bandeja: responde con texto, archivos, botones y plantillas, marca como leído y toma la conversación del bot. Se despliega en Vercel con un clic.
--template inbox - Agente de IARecibe
message:received, contesta con Claude por el mismo canal, respetabot_pausedy deriva a una persona cuando hace falta. Vercel o Railway.--template agent - Solo enviar un mensajeUn archivo de TypeScript con el SDK: el punto de partida más corto.
--template send
Las plantillas son de código abierto (MIT): cámbialas como quieras.
Qué sigue#
- Autenticación y scopesQué permite cada scope, rotación de keys e IPs permitidas.
- Clientes y links de conexiónTus clientes conectan su propio WhatsApp, Instagram, Messenger o Gmail y tú mandas por ellos.
- WebhooksEventos, firma, reintentos y deduplicación.
- Errores y límitesFormato de error, límite de solicitudes, idempotencia y paginación.
- Migrar desde otro proveedorEquivalencias y pasos desde Twilio, 360dialog, YCloud y Kapso.
- Referencia de la APICada endpoint con sus parámetros, respuestas y ejemplos.
¿Programas con un agente de IA?#
Claude Code, Cursor o Codex trabajan mejor con la API en texto plano. Pásale a tu agente estos enlaces:
- llms-full.txt: la API completa, generada desde la especificación.
- llms.txt y AGENTS.md: el índice y las reglas para agentes.
- openapi.json: la especificación OpenAPI 3.1, para generar clientes.
Los cuatro pasos, un prompt listo para tu agente y los comandos para conectar el servidor MCP están en Agentes de código y MCP.