Guías
CLI
@heysteff/cli te da el comando heysteff para usar la API desde la terminal: mandar un mensaje de prueba, revisar conversaciones o probar tu webhook sin escribir código. Está hecho sobre el SDK de TypeScript y necesita Node 20 o más nuevo.
Paquete: @heysteff/cli en npm · código publicado · GitHub de heysteff · licencia MIT. El README del paquete trae un prompt listo para configurarlo con tu IA. Todos los paquetes y repos: Paquetes y repositorios.
Instalar#
Una línea: el instalador revisa que tengas Node 20 o más nuevo y npm, instala @heysteff/cli con npm install -g y te dice el siguiente paso. Si falta Node, te explica cómo instalarlo (nvm, Homebrew o winget) y no toca nada. Puedes leer el script antes de correrlo.
curl -fsSL https://developers.heysteff.com/install.sh | bashO con npm directo: npm install -g @heysteff/cli. Sin instalar: npx @heysteff/cli setup.
Configurar con heysteff setup#
heysteff setup te lleva de cero a un mensaje enviado: abre el navegador para que autorices la CLI en la consola (igual que heysteff login), guarda la API key que recibe, la prueba y te ofrece mandar un mensaje de prueba con el mismo POST /v1/messages de siempre. Con una key sk_test_ el mensaje sale por el sandbox y solo llega a un teléfono unido a él; con una sk_live_ eliges el canal (WhatsApp, Instagram, Messenger, SMS o correo). Si ya tienes una key y prefieres pegarla, usa heysteff setup --key.
heysteff setup
# Pegar una key que ya tienes, sin pasar por el navegador
heysteff setup --keySin terminal interactiva (CI, un script, un agente de código sin una persona al lado) la key va en --api-key o en HEYSTEFF_API_KEY, y el resto se maneja con opciones:
# Guarda la key, la prueba y manda un mensaje de prueba, sin preguntas
heysteff setup --api-key "$HEYSTEFF_API_KEY" --to +56912345678 --yes
# Solo guardar y probar la key, sin abrir el navegador ni mandar nada
heysteff setup --api-key "$HEYSTEFF_API_KEY" --no-open --skip-test --json| Opción | Qué hace |
|---|---|
--key | Te pide una key que ya tienes, sin mostrarla, en vez de abrir el navegador. |
--api-key <key> | Usa esa key: no abre el navegador ni la pide. |
--no-open | No abre el navegador: solo muestra la URL para que la abras tú. |
--channel <canal>, --to <destino>, --text <texto> | El mensaje de prueba. Con --to se manda sin preguntar; el canal por defecto es whatsapp_cloud. |
--skip-test | No ofrece el mensaje de prueba. |
--yes, -y | No hace preguntas de confirmación. Sin --api-key usa la key guardada o HEYSTEFF_API_KEY. |
Si la API rechaza la key, no se guarda y sale con código 3. Si la key queda guardada pero el mensaje de prueba falla, sale con código 1 y muestra el code del error (por ejemplo sandbox_not_a_member).
Iniciar sesión#
heysteff login conecta la CLI con tu cuenta por el navegador, como gh auth login: no tienes que crear ni copiar una API key.
- La CLI le pide un código a heysteff, abre
https://developers.heysteff.com/console/device?code=ABCD-EFGHy muestra el mismo código (ABCD-EFGH) en la terminal. - En esa página, con tu sesión de la consola, revisa que el código sea el de tu terminal y elige el espacio (uno developer, o uno de heysteff si tienes), el modo (producción, con una key
sk_live_, o prueba, con unask_test_del sandbox) y los permisos. Los permisos por defecto ya vienen marcados. - Aprieta Autorizar, o Rechazar si no fuiste tú. Se crea una API key con el nombre
CLIy el de tu equipo; la terminal la recibe una sola vez y la guarda en~/.config/heysteff/config.jsoncon permisos 600 (solo tu usuario la lee), en el perfil que estés usando.
El código vence a los 10 minutos y sirve una sola vez. La key queda visible en la consola, en API keys, y desde ahí la revocas cuando quieras. heysteff whoami dice qué key se está usando, de dónde salió, si es de prueba y si la API la acepta.
heysteff login # abre el navegador y espera que autorices
heysteff login --no-browser # solo imprime la URL: para SSH o un contenedor
heysteff whoami
# Pegar una key que ya tienes (el modo de antes)
heysteff login --key
heysteff login --api-key sk_test_…
echo "$HEYSTEFF_API_KEY" | heysteff login --key
# Varias keys: una por perfil
heysteff login --profile prueba
heysteff whoami --profile prueba
heysteff logout| Opción | Qué hace |
|---|---|
--no-browser | No abre el navegador: imprime la URL y el código para que los abras en otro equipo. |
--key | Te pide una key que ya tienes, sin mostrarla, o la lee de la entrada estándar. |
--api-key <key> | Guarda esa key sin preguntar. |
--skip-verify | Con --key o --api-key: guarda la key sin probarla contra la API. |
--profile <nombre> | Guarda la key en ese perfil en vez del de por defecto. |
En CI y en servidores no uses login: pasa la key en HEYSTEFF_API_KEY. La key se busca en este orden:
| Origen | Ejemplo |
|---|---|
La opción --api-key | heysteff whoami --api-key sk_test_… |
La variable HEYSTEFF_API_KEY | export HEYSTEFF_API_KEY=sk_test_… |
| El perfil guardado | --profile, HEYSTEFF_PROFILE o el de por defecto |
La base de la API se cambia con --base-url o HEYSTEFF_BASE_URL, y la carpeta del archivo con HEYSTEFF_CONFIG_DIR.
Autorizar desde tu propia herramienta#
El inicio de sesión por el navegador usa dos endpoints públicos, al estilo de RFC 8628 (device authorization grant). Con ellos tu propia herramienta pide una key igual que la CLI. Primero pide un código:
curl -X POST https://app.heysteff.com/api/cli/device/code \
-H "Content-Type: application/json" \
-d '{ "client_name": "heysteff-cli", "hostname": "mi-mac" }'{
"device_code": "…",
"user_code": "ABCD-EFGH",
"verification_uri": "https://developers.heysteff.com/console/device",
"verification_uri_complete": "https://developers.heysteff.com/console/device?code=ABCD-EFGH",
"expires_in": 600,
"interval": 5
}Muestra user_code, abre verification_uri_complete y consulta el token cada interval segundos hasta que la persona autorice o rechace:
curl -X POST https://app.heysteff.com/api/cli/device/token \
-H "Content-Type: application/json" \
-d '{ "device_code": "…" }'| Respuesta | Qué hacer |
|---|---|
400 authorization_pending | La persona todavía no responde: vuelve a consultar después de interval segundos. |
400 slow_down | Consultaste antes de tiempo: suma 5 segundos al intervalo. |
400 access_denied | La persona apretó Rechazar. No sigas consultando. |
400 expired_token | Pasaron los 10 minutos: pide un código nuevo. |
400 invalid_grant | El device_code no existe o ya se usó. |
| 200 | Autorizado. El cuerpo trae la key; llega solo en esta respuesta, así que guárdala. |
Los errores tienen el formato de siempre, { "error": { "code": "authorization_pending" } }. La respuesta exitosa:
{
"api_key": "sk_live_…",
"workspace": { "slug": "mi-tienda", "name": "Mi tienda" },
"scopes": ["messages:write", "conversations:read", "events:read"],
"mode": "live"
}mode es live (key sk_live_) o test (key sk_test_ del sandbox).
Mandar mensajes#
Texto, plantillas de WhatsApp y archivos. Con --media y un archivo del disco, el CLI lo sube primero con POST /v1/media y deduce el tipo (imagen, audio, video o documento) de la extensión. Los tipos y límites están en Tipos de mensaje.
heysteff messages send --channel whatsapp_cloud --to +56912345678 --text "Hola Ana, tu pedido ya va en camino"
heysteff messages send --channel whatsapp_cloud --to +56912345678 \
--template pedido_despachado --language es --param Ana --param "#1042" \
--idempotency-key pedido-1042-despacho
heysteff messages send --channel whatsapp_cloud --to +56912345678 \
--media ./catalogo.pdf --text "Te dejo el catálogo de noviembre"Botones, ubicaciones, estado, leído y reacciones:
heysteff messages send --channel whatsapp_cloud --to +56912345678 \
--text "¿Confirmas tu hora?" --buttons "si:Sí,no:No"
heysteff messages send --channel instagram --to 17841400000000000 \
--text "Tienda Centro" --location -33.4489,-70.6693
heysteff messages get 1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f
heysteff messages read 4e3d2c1b-0a9f-4e8d-7c6b-5a4f3e2d1c0b
heysteff messages react 4e3d2c1b-0a9f-4e8d-7c6b-5a4f3e2d1c0b 👍Conversaciones, plantillas y contactos#
Las listas muestran una página y cómo pedir la siguiente; --all las recorre todas.
heysteff conversations list --status open --limit 20
heysteff conversations messages 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
heysteff templates list --status all
heysteff contacts list --q ana
heysteff contacts create --name "Ana Pérez" --phone 912345678 --phone-country CL --tag vip
heysteff media upload ./foto.jpgPara pasar una conversación a una persona del equipo, pausar o reanudar el bot y resolverla (lo que usa tu propia IA, ver Conecta tu propia IA):
heysteff conversations get 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
heysteff conversations escalate 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d --reason "Pide un reembolso" --urgency high
heysteff conversations pause 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
heysteff conversations resume 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
heysteff conversations resolve 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6dNúmeros de WhatsApp de heysteff#
heysteff numbers reserve reserva un número y te da el link para terminar el alta en la ventana de Meta (la API no puede saltarse ese paso). Detalles en Obtén un número.
heysteff numbers list
heysteff numbers reserve # imprime el connect_url
heysteff numbers display-name <id> "Tienda Aurora"
heysteff numbers release <id> # solo una reserva sin conectarWebhooks desde la terminal (el secreto se muestra una sola vez):
heysteff webhooks create --url https://api.tutienda.com/heysteff \
--event message:received --event message.read
heysteff webhooks list
heysteff webhooks delete 6f1c2a8e-3b4d-4e5f-9a0b-1c2d3e4f5a6bClientes y links de conexión#
heysteff customers link imprime una URL de un solo uso donde tu cliente conecta su propio WhatsApp, Instagram, Messenger o Gmail. Detalles en Clientes y links de conexión.
heysteff customers create --external-id acme-42 --name "Acme SpA" --metadata '{"plan":"pro"}'
heysteff customers list --external-id acme-42
heysteff customers get <id>
heysteff customers link <id> --channels whatsapp_cloud,instagram --redirect-url https://tuapp.com/listo
heysteff customers channels <id>
heysteff customers delete <id> # sus canales siguen conectados
heysteff messages send --channel whatsapp_cloud --customer <id> --to +56912345678 --text "Hola"Eventos en vivo con heysteff listen#
heysteff listen te muestra en la terminal, en vivo, lo que pasa en tu espacio: cada mensaje que entra o sale, cada recibo de entrega o lectura, cada escalación. No necesitas un túnel ni un webhook público: la CLI abre una conexión de salida a heysteff (GET /v1/events/stream) con tu API key. Llegan los mismos eventos que a un webhook (message:received, message:sent, message.delivered, message.read, message.failed, conversation.escalated y los demás), de todos los canales: WhatsApp (API oficial y QR), Instagram, Messenger, SMS y Gmail.
heysteff listen14:03:52 message:received 5d1f0c7e whatsapp_cloud +56912345678: Gracias, ¿a qué hora llega?
14:03:58 message:sent 8b0f6a1e whatsapp_cloud +56912345678: Llega entre las 15:00 y las 17:00
14:04:10 message.read 2a9c4d7b whatsapp_cloud 1c2d3e4fCada línea trae la hora, el evento, su id y un resumen. Necesita una key con el scope events:read. Con una key sk_test_ llegan solo los eventos de las conversaciones del sandbox, y basta su scope conversations:read.
Reenviar a tu servidor local#
Con --forward-to, además de mostrar cada evento, la CLI lo manda a tu servidor con el mismo sobre ({ id, tenant_id, event, ts, data }) y las mismas cabeceras que un webhook real: X-Heysteff-Event, X-Heysteff-Timestamp, X-Heysteff-Signature: v1=… y X-Heysteff-Delivery-Id. Así pruebas tu receptor con mensajes de verdad (le escribes a tu número desde tu teléfono) sin exponerlo a internet.
heysteff listen --forward-to http://localhost:3000/webhooks/heysteffSecreto de firma: whsec_local_…
14:03:52 message:received 5d1f0c7e whatsapp_cloud +56912345678: Gracias, ¿a qué hora llega?
→ 200 OK (12 ms)La CLI firma con un secreto local que muestra al iniciar (whsec_local_…), no con el secreto de tus webhooks: configura tu receptor local con ese valor, o fija uno propio con --secret (por ejemplo, el que ya tienes en tu .env). Bajo cada evento ves el status que respondió tu servidor y cuánto tardó. Un receptor completo con el SDK está en Probar en local sin túnel.
heysteff listen --forward-to http://localhost:3000/webhooks/heysteff --secret "$HEYSTEFF_WEBHOOK_SECRET"
# Solo algunos eventos
heysteff listen --events message:received,message.read
heysteff listen --events message.failed --events conversation.escalated
# Una línea JSON por evento (NDJSON), para scripts
heysteff listen --json | jq -r 'select(.event == "message:received") | .data.content'| Opción | Qué hace |
|---|---|
--forward-to <url> | Reenvía cada evento a esa URL, firmado como un webhook, y muestra el status y el tiempo de respuesta. |
--secret <whsec_…> | Secreto para firmar lo que reenvía. Sin esta opción la CLI genera uno (whsec_local_…) y lo muestra al iniciar. |
--events <lista> | Solo esos eventos, separados por coma. Se puede repetir. |
--json | Una línea JSON por evento (NDJSON) con el sobre completo. |
Si se corta la conexión, la CLI se reconecta sola y recupera los eventos que se perdió mientras tanto: heysteff guarda los de los últimos 10 minutos mientras alguien escucha. Para salir, Ctrl+C.
El stream sin la CLI#
GET /v1/events/stream es un stream de Server-Sent Events que puedes leer desde cualquier lenguaje con tu API key, o con heysteff.events.stream() del SDK. Pide los mismos scopes que listen y filtra con ?events=message:received,message.read.
curl -N "https://app.heysteff.com/api/v1/events/stream?events=message:received" \
-H "Authorization: Bearer $HEYSTEFF_API_KEY"retry: 3000
event: ready
data: {"tenant_id":"2c7e9b1a-3d4f-4a5b-8c6d-7e8f9a0b1c2d","mode":"live"}
id: 5d1f0c7e-8a2b-4c3d-9e4f-0a1b2c3d4e5f
event: message:received
data: {"id":"5d1f0c7e-8a2b-4c3d-9e4f-0a1b2c3d4e5f","tenant_id":"2c7e9b1a-3d4f-4a5b-8c6d-7e8f9a0b1c2d","event":"message:received","ts":"2026-11-14T14:03:52.104Z","data":{…}}
: ping- Al conectar llegan
retry: 3000y un eventoreadycon{ tenant_id, mode }. - Cada evento trae
id,event(el nombre) ydata: el mismo sobre JSON que el cuerpo de un webhook. - Cada 25 segundos llega un comentario
: pingpara mantener viva la conexión. - El servidor cierra la conexión a la hora. Reconecta con la cabecera
Last-Event-IDy el últimoidque recibiste: te llega lo que pasó entretanto, hasta 10 minutos atrás.
Probar tu webhook en localhost#
heysteff webhooks trigger arma un aviso de ejemplo con el mismo sobre (id, tenant_id, event, ts, data) y las mismas cabeceras firmadas que manda heysteff (X-Heysteff-Signature, X-Heysteff-Timestamp, X-Heysteff-Event, X-Heysteff-Delivery-Id), y lo envía a tu servidor. Así pruebas tu receptor y su verificación sin exponerlo a internet. El evento por defecto es message:received; --data acepta JSON o @archivo.json.
heysteff webhooks trigger http://localhost:3000/webhooks/heysteff --secret whsec_test_123
heysteff webhooks trigger http://localhost:3000/webhooks/heysteff --secret whsec_test_123 \
--event message:sent --data @evento.jsonheysteff webhooks verify revisa la firma de un aviso que recibiste. Pásale el cuerpo crudo en un archivo o por la entrada estándar, sin reformatear ni agregar un salto de línea. Para revisar un aviso guardado, que ya tiene más de 5 minutos, usa --tolerance 0.
heysteff webhooks verify --secret whsec_test_123 --timestamp 1730000000 \
--signature v1=… --tolerance 0 cuerpo.jsonPara recibir en tu máquina los eventos reales de tu espacio, usa heysteff listen --forward-to.
Salida en JSON y errores#
Todo comando acepta --json y devuelve lo mismo que la API, listo para jq. Un error muestra el code, el message y, cuando la API lo sabe, cómo arreglarlo; con --json va a la salida de errores con el formato { "error": { … } }. Los códigos están en Errores y límites.
heysteff conversations list --all --json | jq -r '.[] | select(.channel == "whatsapp_cloud") | .id'| Código de salida | Significado |
|---|---|
| 0 | Todo bien. |
| 1 | La API respondió con un error, falló la red o tu servidor no respondió 2xx en webhooks trigger. |
| 2 | Faltan opciones o tienen un valor inválido. |
| 3 | No hay API key, o la API la rechazó (401). |
| 4 | webhooks verify: la firma no es válida. |