developers
EN Crear cuenta

Guías

Llamadas de WhatsApp

Tus clientes pueden llamarte por WhatsApp a tu número de WhatsApp Cloud. heysteff registra cada llamada (entrante o saliente, contestada o perdida, con su duración), deja una nota en la conversación y te avisa por webhook. También puedes mandar un botón para que el cliente te llame y pedirle permiso para llamarlo.

Encender las llamadas en un número#

PATCH /v1/whatsapp-numbers/{id}/calling (scope calls:write) enciende o apaga las llamadas. {id} es el channel_connection_id del número. Lo que no mandes queda como está en Meta, y Meta decide si el número es elegible (si no lo es, responde 424 meta_error con el motivo).

curl
curl -X PATCH https://app.heysteff.com/api/v1/whatsapp-numbers/3a2b1c0d-9e8f-4a7b-8c6d-5e4f3a2b1c0d/calling \
  -H "Authorization: Bearer $HEYSTEFF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "call_icon_visible": true,
    "callback_permission": true,
    "call_hours": {
      "timezone_id": "America/Santiago",
      "weekly": [
        { "day": "MONDAY", "open": "0900", "close": "1800" },
        { "day": "TUESDAY", "open": "0900", "close": "1800" }
      ],
      "holidays": [{ "date": "2026-12-25", "start": "0000", "end": "2359" }]
    }
  }'
CampoQué es
enabledObligatorio. Enciende o apaga las llamadas del número.
call_icon_visibleMuestra u oculta el botón de llamar en el chat.
callback_permissionSi el cliente puede autorizar que lo llames de vuelta.
call_hoursEl horario: timezone_id (zona IANA), weekly con hasta 2 tramos por día en formato HHMM de 24 horas, y holidays opcional. null quita el horario (se puede llamar siempre).

GET /v1/whatsapp-numbers/{id}/calling (scope calls:read) devuelve los ajustes en vivo desde Meta, incluido sip_enabled. En la consola, cada número de WhatsApp Cloud de la sección Números tiene sus ajustes de llamadas.

Requisito de Meta: para que lleguen los eventos, la app de Meta del número tiene que tener suscrito el campo de webhook calls. Las llamadas no funcionan en números en coexistencia (el mismo número en la app del celular) ni por WhatsApp por QR.

Recibir los eventos#

Cada llamada avisa una vez por cambio con estos webhooks. Suscríbete a ellos al crear el webhook (o deja el webhook sin eventos para recibirlos todos).

EventoCuándo llega
call.startedEmpezó una llamada: está sonando o ya la contestaron.
call.endedTerminó una llamada que alguien contestó (trae duration_seconds), o falló (status: "failed", con error).
call.missedTerminó sin que nadie contestara, o la persona la rechazó (status: "rejected").
JSON
{
  "event": "call.ended",
  "data": {
    "call_id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    "wa_call_id": "wacid.HBgLMTU1NTAxMDAxOTkVAgARGBI…",
    "channel_connection_id": "3a2b1c0d-9e8f-4a7b-8c6d-5e4f3a2b1c0d",
    "conversation_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "contact_id": "1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b",
    "direction": "inbound",
    "status": "completed",
    "customer": "56912345678",
    "started_at": "2026-11-14T15:02:11.000Z",
    "ended_at": "2026-11-14T15:05:40.000Z",
    "duration_seconds": 209,
    "error": null
  }
}

Registro de llamadas#

GET /v1/calls (scope calls:read) lista las llamadas de la más nueva a la más vieja, con channel_connection_id, status (ringing, answered, completed, missed, rejected, failed) y direction (inbound, outbound) como filtros. Para la página siguiente, manda before con el next_before de la respuesta. En la consola, la sección Llamadas muestra lo mismo.

curl
curl "https://app.heysteff.com/api/v1/calls?status=missed&limit=20" \
  -H "Authorization: Bearer $HEYSTEFF_API_KEY"
TypeScript
const { data, next_before } = await heysteff.calls.list({ status: 'missed', limit: 20 });
await heysteff.whatsappNumbers.updateCalling(channelConnectionId, { enabled: true, call_hours: null });

Botón «Llamar» y permiso de llamada#

Dentro de la ventana de 24 horas, content_type: "voice_call" manda un mensaje con un botón para que el cliente te llame por WhatsApp. No hace la llamada por ti: el que llama es el cliente.

json
{
  "channel": "whatsapp_cloud",
  "to": "+56912345678",
  "content_type": "voice_call",
  "voice_call": {
    "body": "¿Prefieres hablarlo? Llámanos directo por WhatsApp.",
    "display_text": "Llamar ahora",
    "ttl_minutes": 1440,
    "payload": "caso-881"
  }
}

body es obligatorio (hasta 1.024 caracteres); display_text, hasta 20 caracteres (por defecto, el texto de WhatsApp); ttl_minutes, de 1 a 43.200 minutos (por defecto, 7 días); payload, hasta 512 caracteres.

content_type: "call_permission_request" le pide al cliente permiso para que tu negocio lo llame:

json
{
  "channel": "whatsapp_cloud",
  "to": "+56912345678",
  "content_type": "call_permission_request",
  "call_permission": { "body": "¿Te podemos llamar por WhatsApp para coordinar la entrega?" }
}

Fuera de las 24 horas, usa una plantilla con botón de llamar: se crea con POST /v1/templates y un botón {"type": "VOICE_CALL", "text": "Llamar", "ttl_minutes": 10080} (en una plantilla, entre 1 y 30 días), y se manda con este componente:

json
{
  "type": "button",
  "sub_type": "voice_call",
  "parameters": [
    { "type": "ttl_minutes", "ttl_minutes": 10080 },
    { "type": "payload", "payload": "caso-881" }
  ]
}

Los tres son solo de WhatsApp Cloud (en otro canal responden 422 whatsapp_cloud_only) y necesitan una key sk_live_ (una de prueba responde 403 test_key_not_allowed). Si Meta rechaza el mensaje, queda failed y no se reemplaza por texto.

Límites#

Qué no hace todavía#

heysteff registra las llamadas, pero todavía no las contesta, no las rechaza y no lleva el audio: no es un servidor de medios. Si hoy necesitas contestar llamadas desde tu sistema, hay dos caminos con Meta, por tu cuenta:

Contestar desde heysteff (en la bandeja y por API) está en la lista de pendientes.