← Frepi Developers

API de WhatsApp en una línea — formato Cloud API de Meta

Una línea de la API de WhatsApp · Autenticación Bearer con una llave frl_live_… · Webhook firmado con X-Hub-Signature-256

Conecta tu propio servidor a una línea de WhatsApp de Frepi. Usa el mismo formato de la Cloud API de Meta: en tu código solo cambian tres cosas — la dirección, la llave y el secreto del webhook. Cada envío de tu servidor aparece en Conversaciones de Frepi, con la marca «API».

1 · Activar la API en una línea

2 · Qué cambia frente a Meta: tres cosas

Cloud API de MetaFrepi
Direcciónhttps://graph.facebook.com/v27.0https://app.frepi.ai/api/meta/v27.0
AutorizaciónToken de MetaTu llave de Frepi, en el mismo encabezado Authorization: Bearer
Firma del webhookSecreto de tu app de MetaEl secreto de Frepi, en el mismo encabezado X-Hub-Signature-256

El segmento de versión (v27.0) se acepta con el formato v + número + punto + dígito, y se ignora: Frepi llama a Meta con su propia versión. El encabezado Host no cambia nada: la línea sale de la llave. Los ids son los de Meta: el phone_number_id y el id de la cuenta (WABA) de tu línea los ves en la línea en Frepi.

3 · La llave

Authorization: Bearer frl_live_…

4 · Qué puede llamar tu servidor

Para quéMétodo y ruta (igual que en la Cloud API de Meta)
Enviar texto y plantillas (a una persona o a un grupo)POST /{phone_number_id}/messages
Bajar archivos recibidosGET /{media_id} · GET /{media_id}/download
Crear y borrar gruposPOST /{phone_number_id}/groups · DELETE /{group_id}
Aprobar pedidos de entrada a un grupoPOST /{group_id}/join_requests
Crear y listar plantillasPOST · GET /{waba_id}/message_templates

Cualquier otra ruta responde 403 (código 10), con el formato de error de Meta. No hay ruta para subscribed_apps, register, deregister, el PIN de verificación en dos pasos, otro número, otra cuenta o la app. Todas las respuestas llevan Cache-Control: private, no-store.

Enviar un texto

curl -X POST https://app.frepi.ai/api/meta/v27.0/$PHONE_NUMBER_ID/messages \
  -H "Authorization: Bearer $FREPI_LINE_KEY" \
  -H "X-Frepi-Idempotency-Key: pedido-8812" \
  -H "Content-Type: application/json" \
  -d '{"messaging_product":"whatsapp","recipient_type":"individual","to":"573001234567",
       "type":"text","text":{"body":"Hola Ana, tu pedido salió hoy."}}'
{"messaging_product":"whatsapp","contacts":[{"input":"573001234567","wa_id":"573001234567"}],
 "messages":[{"id":"wamid.HBgM…"}]}

wa_id repite el to que enviaste (sin el +). Frepi no aplica la ventana de 24 horas: el envío va a Meta, y si la ventana está cerrada Meta lo dice en el estado del webhook (131047), igual que en Meta. context.message_id se acepta pero todavía no se envía como respuesta citada.

Enviar una plantilla

curl -X POST https://app.frepi.ai/api/meta/v27.0/$PHONE_NUMBER_ID/messages \
  -H "Authorization: Bearer $FREPI_LINE_KEY" -H "Content-Type: application/json" \
  -d '{"messaging_product":"whatsapp","to":"573001234567","type":"template",
       "template":{"name":"pedido_enviado","language":{"code":"es"},
                   "components":[{"type":"body","parameters":[{"type":"text","text":"Ana"},{"type":"text","text":"8812"}]}]}}'

La plantilla debe estar aprobada y ser de esta línea (o de la cuenta, sin línea). Por ahora solo se aceptan parámetros del body (con parameter_name en una plantilla con nombres), y el botón de la plantilla de invitación a un grupo (abajo). Cualquier otro parámetro responde 400 código 132000.

Enviar a un grupo

curl -X POST https://app.frepi.ai/api/meta/v27.0/$PHONE_NUMBER_ID/messages \
  -H "Authorization: Bearer $FREPI_LINE_KEY" -H "Content-Type: application/json" \
  -d '{"messaging_product":"whatsapp","recipient_type":"group","to":"'$GROUP_ID'",
       "type":"text","text":{"body":"Hola a todos"}}'

El grupo debe ser de esta línea y estar activo. La invitación a un grupo se envía a una persona con la plantilla de invitación de la línea y el enlace (o el código) del grupo como parámetro de su botón: {"type":"button","sub_type":"url","index":"0","parameters":[{"type":"text","text":"<código>"}]}. El enlace debe ser el actual de un grupo de esta línea.

Crear, borrar un grupo y aprobar pedidos de entrada

curl -X POST https://app.frepi.ai/api/meta/v27.0/$PHONE_NUMBER_ID/groups \
  -H "Authorization: Bearer $FREPI_LINE_KEY" -H "Content-Type: application/json" \
  -d '{"messaging_product":"whatsapp","subject":"Clientes Midas","description":"Novedades",
       "join_approval_mode":"approval_required"}'

curl -X POST https://app.frepi.ai/api/meta/v27.0/$GROUP_ID/join_requests \
  -H "Authorization: Bearer $FREPI_LINE_KEY" -H "Content-Type: application/json" \
  -d '{"messaging_product":"whatsapp","join_requests":["'$JOIN_REQUEST_ID'"]}'

curl -X DELETE https://app.frepi.ai/api/meta/v27.0/$GROUP_ID \
  -H "Authorization: Bearer $FREPI_LINE_KEY"

Crear responde el id del grupo, o el request_id cuando Meta lo crea después (el id llega entonces en el evento group_lifecycle_update del webhook). Sin join_approval_mode, entra quien tenga el enlace. Aprobar responde {"messaging_product":"whatsapp","approved_join_requests":[…]}.

Plantillas

curl "https://app.frepi.ai/api/meta/v27.0/$WABA_ID/message_templates?status=APPROVED&limit=50" \
  -H "Authorization: Bearer $FREPI_LINE_KEY"

curl -X POST https://app.frepi.ai/api/meta/v27.0/$WABA_ID/message_templates \
  -H "Authorization: Bearer $FREPI_LINE_KEY" -H "Content-Type: application/json" \
  -d '{"name":"pedido_enviado","language":"es","category":"UTILITY",
       "components":[{"type":"BODY","text":"Hola {{1}}, tu pedido {{2}} salió hoy.",
                      "example":{"body_text":[["Ana","8812"]]}}]}'

La lista trae las plantillas que ya están en Meta: las de esta línea y las de su cuenta (WABA), nunca las de otra cuenta del espacio (filtros name, status, language; limit hasta 200). Se pagina como en Meta: paging.cursors.before / after, y los enlaces paging.next / paging.previous cuando hay otra página (o envía after= / before= con un cursor). Crear responde {"id","status":"PENDING","category"}. Por ahora no se crean plantillas con encabezado de imagen, video o documento (400, media_header_unsupported), ni con parámetros en el encabezado o los botones.

Archivos recibidos

curl https://app.frepi.ai/api/meta/v27.0/$MEDIA_ID -H "Authorization: Bearer $FREPI_LINE_KEY"
curl -o archivo https://app.frepi.ai/api/meta/v27.0/$MEDIA_ID/download -H "Authorization: Bearer $FREPI_LINE_KEY"

El media_id es el que llegó en un evento del webhook de esta línea. La respuesta es la de Meta, con la url cambiada a la descarga de Frepi (la URL de Meta nunca sale de Frepi). La descarga llega como adjunto (Content-Disposition: attachment, X-Content-Type-Options: nosniff). Hasta 100 MB por archivo y 4 descargas a la vez por línea. Si un archivo pasa los 100 MB sin declarar su tamaño, la descarga se corta con error (respuesta incompleta): no la guardes como completa.

5 · Errores

Siempre el formato de Meta:

{"error":{"message":"…","type":"OAuthException","code":100,"error_subcode":33,
          "error_data":{"messaging_product":"whatsapp","details":"…"},"is_transient":false,"fbtrace_id":"…"}}
CasoHTTPcode / subcodeerror_data.details
Llave ausente, desconocida o revocada401190—
Llave vencida401190 / 463key_expired
API apagada, línea no habilitada, facturación pendiente, ruta no disponible40310not_permitted
Un id que no es de esta línea, o que no existe (la misma respuesta)400100 / 33—
Cuerpo inválido400100el campo
Demasiadas llamadas4294 (is_transient: true)rate_limited
Contacto excluido en Frepi40310recipient_excluded
X-Frepi-Idempotency-Key repetida con otro cuerpo400100idempotency_key_reused
No se sabe si el envío salió500131000 (is_transient: false)outcome_unknown
Meta rechazó el envío400el código de Metala clase de falla de Frepi
Frepi no puede enviar ahora (envíos pausados, almacenamiento)5032 (is_transient: true)el motivo
Parámetros de plantilla no soportados400132000parameters_unsupported

Ante outcome_unknown no reenvíes: el mensaje puede haber salido. Mira el estado en el webhook. Los errores que Meta da después del envío (131047, 131026, …) llegan en el estado del webhook, byte a byte.

6 · Idempotencia

Meta no tiene clave de idempotencia: sin encabezado, cada llamada es un envío nuevo. Con el encabezado de Frepi X-Frepi-Idempotency-Key (8 a 64 caracteres de A-Z a-z 0-9 _ -), repetir la llamada con el mismo cuerpo responde el mismo wamid sin un segundo envío. Con otro cuerpo, otra plantilla u otro destinatario (to) responde idempotency_key_reused. Dos llamadas simultáneas con la misma clave y destinatarios distintos pueden enviarse las dos: no reutilices una clave para otro destinatario. Mientras el primer envío está en curso o sin confirmar responde outcome_unknown. En la creación de un grupo, el mismo encabezado evita crear dos grupos.

7 · Límite de llamadas

20 llamadas por segundo por línea (ráfaga de 40), todas las rutas juntas, también las rechazadas, y también durante las 24 horas en que la llave anterior y la nueva conviven tras una rotación (comparten el límite). Una llave revocada o vencida tiene su propio límite pequeño (1 por segundo) y nunca gasta el de la llave activa. Por encima: 429, código 4, Retry-After: 1. Un cuerpo de más de 256 KB responde 400. Meta permite 80 mensajes por segundo por número, así que el límite de Frepi es el que manda.

8 · El webhook

Frepi envía a tu dirección un POST con el cuerpo que Meta envió para este número, filtrado a esta línea (solo los campos que elegiste: mensajes y estados, grupos, plantillas, calidad del número).

Verificar la firma

X-Hub-Signature-256: sha256=<hex HMAC-SHA256(secreto, cuerpo)>      ← igual que Meta
X-Frepi-Delivery: <uuid>                                          ← el mismo en cada reintento
X-Frepi-Timestamp: <unix>
X-Frepi-Signature: t=<unix>,v1=<hex HMAC-SHA256(secreto, "<unix>." + cuerpo)>

El esquema de Meta no tiene marca de tiempo. Si quieres protegerte de repeticiones, revisa que t esté a menos de 5 minutos y descarta un X-Frepi-Delivery que ya procesaste.

Responder rápido

9 · Lo que Frepi hace y lo que no

Ayuda: escribe al equipo de Frepi desde la plataforma. Esta guía cubre la primera versión; los grupos (leer, editar, enlace, participantes), la subida de archivos y «marcar como leído» llegan después.