developers
EN Crear cuenta

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.

Terminal
curl -fsSL https://developers.heysteff.com/install.sh | bash

O 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.

Terminal
heysteff setup

# Pegar una key que ya tienes, sin pasar por el navegador
heysteff setup --key

Sin 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:

Terminal
# 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ónQué hace
--keyTe 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-openNo 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-testNo ofrece el mensaje de prueba.
--yes, -yNo 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.

  1. La CLI le pide un código a heysteff, abre https://developers.heysteff.com/console/device?code=ABCD-EFGH y muestra el mismo código (ABCD-EFGH) en la terminal.
  2. 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 una sk_test_ del sandbox) y los permisos. Los permisos por defecto ya vienen marcados.
  3. Aprieta Autorizar, o Rechazar si no fuiste tú. Se crea una API key con el nombre CLI y el de tu equipo; la terminal la recibe una sola vez y la guarda en ~/.config/heysteff/config.json con 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.

Terminal
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ónQué hace
--no-browserNo abre el navegador: imprime la URL y el código para que los abras en otro equipo.
--keyTe 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-verifyCon --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:

OrigenEjemplo
La opción --api-keyheysteff whoami --api-key sk_test_…
La variable HEYSTEFF_API_KEYexport 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
curl -X POST https://app.heysteff.com/api/cli/device/code \
  -H "Content-Type: application/json" \
  -d '{ "client_name": "heysteff-cli", "hostname": "mi-mac" }'
JSON
{
  "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
curl -X POST https://app.heysteff.com/api/cli/device/token \
  -H "Content-Type: application/json" \
  -d '{ "device_code": "…" }'
RespuestaQué hacer
400 authorization_pendingLa persona todavía no responde: vuelve a consultar después de interval segundos.
400 slow_downConsultaste antes de tiempo: suma 5 segundos al intervalo.
400 access_deniedLa persona apretó Rechazar. No sigas consultando.
400 expired_tokenPasaron los 10 minutos: pide un código nuevo.
400 invalid_grantEl device_code no existe o ya se usó.
200Autorizado. 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:

JSON
{
  "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.

Terminal
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:

terminal
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.

Terminal
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.jpg

Para 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):

Terminal
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-1e0f9a8b7c6d

Nú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.

Terminal
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 conectar

Webhooks desde la terminal (el secreto se muestra una sola vez):

terminal
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-1c2d3e4f5a6b

Clientes 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.

Terminal
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.

Terminal
heysteff listen
Salida
14: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 1c2d3e4f

Cada 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.

Terminal
heysteff listen --forward-to http://localhost:3000/webhooks/heysteff
Salida
Secreto 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.

Terminal
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ónQué 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.
--jsonUna 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
curl -N "https://app.heysteff.com/api/v1/events/stream?events=message:received" \
  -H "Authorization: Bearer $HEYSTEFF_API_KEY"
Stream
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

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.

Terminal
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.json

heysteff 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.

Terminal
heysteff webhooks verify --secret whsec_test_123 --timestamp 1730000000 \
  --signature v1=… --tolerance 0 cuerpo.json

Para 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.

Terminal
heysteff conversations list --all --json | jq -r '.[] | select(.channel == "whatsapp_cloud") | .id'
Código de salidaSignificado
0Todo bien.
1La API respondió con un error, falló la red o tu servidor no respondió 2xx en webhooks trigger.
2Faltan opciones o tienen un valor inválido.
3No hay API key, o la API la rechazó (401).
4webhooks verify: la firma no es válida.