developers
EN Crear cuenta

Guías

Compatible con la Cloud API de Meta

Si ya tienes código hecho para la Cloud API de WhatsApp (graph.facebook.com/v21.0/{phone_number_id}/messages), no tienes que reescribirlo: cambia la URL base por la de heysteff y el token por tu API key, y sigue funcionando. Cada mensaje pasa por heysteff igual que uno de POST /v1/messages: queda en la bandeja de la web y de la app, cuenta para tu plan, respeta la ventana de 24 horas y aparece en los Logs de la API.

Lo único que cambia#

Meta directoheysteff
URL basehttps://graph.facebook.com/v21.0https://app.heysteff.com/api/meta/whatsapp/v21.0
TokenAuthorization: Bearer EAAG… (token de sistema de Meta)Authorization: Bearer sk_live_… (tu API key de heysteff)
VersiónLa que usesCualquier v<n>.<n>: v21.0, v22.0, v24.0… heysteff responde igual con todas.
Antes
curl https://graph.facebook.com/v21.0/106540352242922/messages \
  -H "Authorization: Bearer $META_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "messaging_product": "whatsapp", "to": "56912345678", "type": "text", "text": { "body": "Hola Ana" } }'
Después
curl https://app.heysteff.com/api/meta/whatsapp/v21.0/106540352242922/messages \
  -H "Authorization: Bearer $HEYSTEFF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "messaging_product": "whatsapp", "to": "56912345678", "type": "text", "text": { "body": "Hola Ana" } }'

La respuesta tiene la forma de Meta:

200
{
  "messaging_product": "whatsapp",
  "contacts": [{ "input": "56912345678", "wa_id": "56912345678" }],
  "messages": [{ "id": "6f1d2c1e-6a1b-4c43-9a51-2f9f6a0e8b7d" }]
}

Con un cliente de la Cloud API que deje cambiar la URL base (el SDK de Node de Meta, whatsapp-api-js o tu propio wrapper de fetch) solo configuras esa URL y el token.

El phone_number_id#

Es el mismo id de Meta de tu número de WhatsApp Cloud, ahora conectado a heysteff: lo ves en la consola, sección Números, y en GET /{phone_number_id}. Sirve cualquier número de WhatsApp Cloud de tu espacio: uno que conectaste tú, un número de heysteff o el que conectó uno de tus clientes con su link de conexión. Un id que no es de tu espacio responde el mismo error que Graph para un objeto ajeno (código 100, subcódigo 33).

Sandbox. Con una key sk_test_, el phone_number_id es el del número compartido de heysteff (lo muestra la consola, sección Sandbox) y solo puedes escribir a los teléfonos que se unieron. También solo ves la media del sandbox: lo que subiste con una key sk_test_ y lo que te mandaron al número compartido; cualquier otro archivo responde 403 test_key_not_allowed. Ver Sandbox.

Qué cubre#

RutaPara quéScope
POST /{phone_number_id}/messagesMandar texto, imagen, audio, video, documento, sticker, ubicación, contactos, reacción, interactivos (botones, lista, cta_url, location_request_message) y plantillas. También marcar leído con status: "read" y typing_indicator.messages:write
POST /{phone_number_id}/mediaSubir un archivo (multipart, campo file). Devuelve { "id": "…" }.messages:write
GET /{media_id}url, mime_type, sha256 y file_size. La url se descarga con el mismo Bearer.messages:write
DELETE /{media_id}Borrar un archivo que subiste con POST /{phone_number_id}/media. El resto de la media de tu espacio no se borra por aquí.messages:write
GET /{phone_number_id}Nombre verificado, número visible, calidad y plataforma.messages:write
GET y POST /{phone_number_id}/whatsapp_business_profileLeer y cambiar el perfil de empresa (about, dirección, descripción, correo, sitios, rubro).messages:write para leer, numbers:write para cambiar
GET /{waba_id}/phone_numbersLos números de esa WABA conectados a tu espacio.messages:write
GET, POST y DELETE /{waba_id}/message_templatesListar, crear (van a revisión de Meta) y borrar plantillas (?name=).messages:write para listar, templates:write para crear y borrar

No están las rutas de administración de la cuenta de Meta (registrar un número, verificación en dos pasos, suscripciones de apps, analítica de la WABA): eso lo hace heysteff por ti al conectar el número. Para todo lo que no es de WhatsApp (Instagram, Messenger, SMS, correo) usa la API de heysteff.

Ids de mensaje#

Cómo se comportan los envíos#

Webhooks en formato Meta#

Para que tu receptor de webhooks de Meta funcione sin cambios, crea el webhook con format: "meta" (en la consola, sección Webhooks, elige "Cloud API de Meta", o por API):

curl
curl https://app.heysteff.com/api/v1/webhooks \
  -H "Authorization: Bearer $HEYSTEFF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://api.tuapp.com/whatsapp/webhook", "format": "meta" }'

Ese webhook recibe el payload de la Cloud API: object: "whatsapp_business_account" y entry[].changes[].value con metadata.phone_number_id, contacts y messages (entrantes) o statuses (sent, delivered, read, failed). Solo llegan los eventos de mensajes de WhatsApp Cloud (message:received, message:sent, message.delivered, message.read, message.failed); los demás canales y eventos siguen en el formato de heysteff.

Firma. Va en X-Hub-Signature-256: sha256=<hex>, el HMAC-SHA256 del cuerpo crudo, igual que la de Meta. La diferencia es la clave: en vez del app secret de tu app de Meta, usa el secreto que te dio heysteff al crear el webhook.

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

// rawBody: el cuerpo tal como llegó. secret: el del webhook de heysteff.
function isFromHeysteff(rawBody, header, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(header ?? '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

heysteff no hace la verificación inicial de Meta (el GET con hub.challenge): la URL queda activa al crearla. El botón "Enviar evento de prueba" de la consola manda un mensaje de texto de ejemplo con la forma de Meta y la misma firma.

Errores#

Los errores tienen la forma de la Graph API, con los códigos de la Cloud API donde existe uno equivalente:

400
{
  "error": {
    "message": "(#100) Invalid parameter",
    "type": "OAuthException",
    "code": 100,
    "error_data": { "messaging_product": "whatsapp", "details": "text.body: Required" },
    "fbtrace_id": "Az8or2yhqkZfEZ-_4Qn_Bam",
    "heysteff_code": "validation_error"
  }
}
CódigoCuándo
190 (401)La API key falta, no existe, se revocó o venció.
200 (403)A la key le falta el scope, o es una key de prueba en algo que no es del sandbox.
100Parámetro inválido; con subcódigo 33, un id que no existe o no es de tu espacio.
130429 (429)Límite de llamadas por minuto de la key, o el tope diario del sandbox.
131030Sandbox: el teléfono no se unió.
131042Tu plan llegó al tope de mensajes del mes.
131047La ventana de 24 horas está cerrada.
131053El archivo no existe o no se pudo bajar.
132000, 132001La plantilla no existe, no está aprobada o le faltan variables.
2 (424)Meta no respondió al leer o cambiar el perfil o una plantilla. Reintenta.

heysteff_code es el código de heysteff del mismo error (ver Errores y límites); los clientes hechos para Meta lo ignoran.

Cuándo usar cada API#

Esta API sirve para mover lo que ya tienes sin tocarlo. Para código nuevo conviene POST /v1/messages: con el mismo cuerpo mandas por WhatsApp, Instagram, Messenger, SMS o correo cambiando solo channel. Las dos conviven en el mismo espacio y con la misma key.