Saltar al contenido
WbizTool

API v2 (beta)

Mensajes

Envía un mensaje ahora o a una hora fijada, a un número de teléfono, un grupo de WhatsApp, un contacto guardado o un nombre de usuario de Telegram. Envía el mismo mensaje a hasta 1.000 destinatarios en una sola solicitud, lee el historial, sigue el estado de un mensaje y cancélalo mientras espera.

OperaciónEndpointPermiso
Enviar un mensajePOST /api/v2/messagesmessages:send
Enviar a muchos destinatariosPOST /api/v2/messages/batchmessages:send
Listar mensajesGET /api/v2/messagesmessages:read
Obtener un mensajeGET /api/v2/messages/{id}messages:read
Cancelar un mensajeDELETE /api/v2/messages/{id}messages:send

Las claves, los errores, Idempotency-Key y los límites funcionan como se describe en la introducción a la API v2. Para saber cuándo se envía o falla un mensaje sin consultarlo una y otra vez, añade un endpoint de webhook.

Enviar un mensaje#

POSThttps://wbiztool.com/api/v2/messages

Cuerpo: JSON (application/json)

El mensaje se pone en cola y la respuesta llega antes de que salga, con HTTP 202 y el Message en estado queued (o scheduled con un send_at futuro). Síguelo con Obtener un mensaje o con un webhook.

curl -X POST https://wbiztool.com/api/v2/messages \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-ready" \
  -d '{
    "from": 101,
    "to": { "phone": "+919876543210" },
    "text": "Hi Asha, your order #1042 is ready."
  }'

Respuesta (202):

{
  "id": 9817263,
  "status": "queued",
  "from": { "id": 101, "phone": "919800000000", "platform": "whatsapp" },
  "to": { "phone": "+919876543210", "contact_id": 991 },
  "text": "Hi Asha, your order #1042 is ready.",
  "media": null,
  "send_at": null,
  "expires_at": null,
  "created_at": "2026-10-18T10:15:02.311Z",
  "sent_at": null,
  "error": null
}

Cuerpo

toobjectobligatorio

Quién lo recibe: exactamente uno de phone, group, contact_id o username. Consulta Destinatarios.

frominteger or nullObligatorio salvo que el espacio de trabajo tenga un solo número conectado

El id del número que lo envía, de Listar números. Si lo omites, lo envía el único número conectado: se usa el único número de WhatsApp conectado aunque también haya una cuenta de Telegram conectada, y una cuenta de Telegram solo cuando no hay ningún número de WhatsApp conectado. Se acepta un número con la sesión cerrada: sus mensajes esperan hasta que se vuelva a conectar.

textstringObligatorio salvo que se envíen medios

El mensaje, hasta 3.000 caracteres. Con media, es el pie de foto y se puede omitir. El formato de WhatsApp funciona: *bold*, _italic_, ~strikethrough~. En JSON, escribe los saltos de línea como \n.

mediaobject or nullopcional

Una imagen o un archivo para enviar. Consulta Medios.

send_atstring or nullopcional

Enviar a esta hora en lugar de ahora: ISO 8601 con su desfase horario, como 2026-10-20T09:30:00+05:30. Debe estar en el futuro. Consulta Programación y caducidad.

expires_atstring or nullopcional

No enviar después de esta hora: en ese caso el mensaje pasa a expired. ISO 8601 con su desfase horario, en el futuro y después de send_at.

Cabeceras

Authorizationheaderobligatorio

Bearer wbz_live_…, una clave con el permiso messages:send.

Idempotency-Keyheaderopcional

Cualquier cadena única, para que un reintento nunca envíe un segundo mensaje. Consulta Idempotency-Key.

Errores al enviar#

HTTPCódigoCuándo
400validation_failedFalta un campo, tiene un tipo incorrecto o no se conoce; to no tiene ninguno de sus campos o tiene varios; media tiene a la vez url y media_id, o ninguno de los dos; expires_at no es posterior a send_at
400text_requiredNo hay text ni media
400text_too_longtext supera los 3.000 caracteres
400invalid_datetimesend_at o expires_at no está en ISO 8601 con desfase horario
402no_creditsAl espacio de trabajo no le quedan mensajes en su plan
403number_not_allowedLa clave está limitada a otros números
403account_disabledLa cuenta del propietario del espacio de trabajo está desactivada
422invalid_phoneto.phone no es un número de teléfono en formato internacional con +
422invalid_usernameto.username no es un nombre de usuario de Telegram, un enlace ni un ID tg:
422username_needs_telegramto.username con un número de WhatsApp como from
422contact_not_foundNo hay ningún contacto con ese to.contact_id
422media_not_foundNo hay ningún archivo con ese media.media_id
422invalid_media_urlmedia.url no es un enlace http(s) público
422time_in_pastsend_at o expires_at ya pasó
422number_not_foundfrom no es un número de este espacio de trabajo
422from_requiredNo hay from, y el espacio de trabajo no tiene ningún número conectado o tiene varios
422message_rejectedEl mensaje no se pudo poner en cola; message indica el motivo
429number_daily_cap_reachedEl número ya envió 1.000 mensajes en las últimas 24 horas

Un envío rechazado no deja nada: no se guarda ningún mensaje, así que no hay nada que cancelar. Los errores que pueden ocurrir en cualquier solicitud (claves, permisos, límites de solicitudes, Idempotency-Key) están en la página de errores.

Enviar a muchos destinatarios#

POSThttps://wbiztool.com/api/v2/messages/batch

Cuerpo: JSON (application/json)

Envía el mismo mensaje a hasta 1.000 destinatarios. El cuerpo es el mismo que en Enviar un mensaje, salvo que to es una lista de destinatarios.

  • Todo o nada. Se comprueban todos los destinatarios antes de poner nada en cola, y la lista completa debe caber en los mensajes que le quedan al plan y en el margen del número que envía para las próximas 24 horas. Si un destinatario es incorrecto, no se pone nada en cola, y field lo indica, como to[3].phone.
  • El mismo destinatario dos veces recibe un solo mensaje.
  • La respuesta es 202 con data: un Message por destinatario, en el orden de to.
curl -X POST https://wbiztool.com/api/v2/messages/batch \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: opening-hours-2026-10-20" \
  -d '{
    "from": 101,
    "to": [
      { "phone": "+919876543210" },
      { "phone": "+447700900123" },
      { "group": "Bakery staff" }
    ],
    "text": "We open at 10 tomorrow."
  }'

Respuesta (202), un Message por destinatario; el segundo y el tercero son como el primero, con to igual a {"phone": "+447700900123", …} y {"group": "Bakery staff", …}:

{
  "data": [
    {
      "id": 9817264,
      "status": "queued",
      "from": { "id": 101, "phone": "919800000000", "platform": "whatsapp" },
      "to": { "phone": "+919876543210", "contact_id": 991 },
      "text": "We open at 10 tomorrow.",
      "media": null,
      "send_at": null,
      "expires_at": null,
      "created_at": "2026-10-19T16:02:40.118Z",
      "sent_at": null,
      "error": null
    }
  ]
}

Errores al enviar un lote#

Los mismos que al enviar un mensaje, con la posición del destinatario en field (to[0].phone), y además:

HTTPCódigoCuándo
400too_many_recipientsto tiene más de 1.000 entradas
400validation_failedto está vacío o no es una lista
402no_creditsLa lista no cabe en los mensajes que quedan: requested y remaining indican cuánto falta
429number_daily_cap_reachedLa lista no cabe en el margen del número para las próximas 24 horas: requested y room indican cuántos

Listar mensajes#

GEThttps://wbiztool.com/api/v2/messages

Los mensajes del espacio de trabajo de los últimos 90 días, primero los más recientes, en páginas. Incluye los mensajes enviados desde el panel, las campañas, los recordatorios, las integraciones y la API v1, no solo los de la API v2. Una clave limitada a algunos números solo ve los mensajes de esos números.

Parámetros de consulta

numberintegeropcional

Solo los mensajes de este id de número.

statusstringopcional

Solo los mensajes con este estado: queued, scheduled, sent, delivered, read, failed, cancelled o expired.

sincestringopcional

Solo los mensajes creados a esta hora o después (ISO 8601 con desfase horario).

untilstringopcional

Solo los mensajes creados antes de esta hora (ISO 8601 con desfase horario).

limitintegeropcional

Mensajes por página, de 1 a 100 (25 por defecto).

cursorstringopcional

El next_cursor de la última página, con los mismos filtros.

Los filtros se combinan. En una cadena de consulta, escribe el + de un desfase horario como %2B (un + sin codificar llega como un espacio, que también se lee como +), o usa Z.

curl -G https://wbiztool.com/api/v2/messages \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY" \
  --data-urlencode "status=failed" \
  --data-urlencode "since=2026-10-01T00:00:00+05:30" \
  --data-urlencode "limit=100"

La respuesta es {"data": [Message, …], "next_cursor": "…" or null}.

Errores al listar#

HTTPCódigoCuándo
400validation_failednumber no es un ID de número, status no es uno de los estados o limit no está entre 1 y 100
400invalid_datetimesince o until no está en ISO 8601 con desfase horario
400invalid_cursorEl cursor está dañado o se creó con otros filtros

Obtener un mensaje#

GEThttps://wbiztool.com/api/v2/messages/{id}

Un mensaje y su estado actual.

curl https://wbiztool.com/api/v2/messages/9817263 \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY"

Respuesta (200), un mensaje que se entregó:

{
  "id": 9817263,
  "status": "delivered",
  "from": { "id": 101, "phone": "919800000000", "platform": "whatsapp" },
  "to": { "phone": "+919876543210", "contact_id": 991 },
  "text": "Hi Asha, your order #1042 is ready.",
  "media": null,
  "send_at": null,
  "expires_at": null,
  "created_at": "2026-10-18T10:15:02.311Z",
  "sent_at": "2026-10-18T10:15:09.870Z",
  "error": null
}

Un mensaje que no existe, que pertenece a otro espacio de trabajo o que (para una clave limitada a algunos números) se envió desde otro número responde 404 message_not_found.

Cancelar un mensaje#

DELETEhttps://wbiztool.com/api/v2/messages/{id}

Cancela un mensaje que todavía está en espera (queued o scheduled), lo haya creado quien lo haya creado. La respuesta es 200 con el Message, ahora en estado cancelled. Un endpoint de webhook recibe message.cancelled.

Un mensaje que ya salió no se puede recuperar. Un mensaje que el número que envía está recogiendo en ese momento aún puede salir, así que comprueba su estado después si es importante.

curl -X DELETE https://wbiztool.com/api/v2/messages/9817263 \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY"

Errores al cancelar#

HTTPCódigoCuándo
404message_not_foundNo existe ese mensaje para esta clave
409not_cancellableYa no está en espera; status en el error indica en qué estado está ahora, como sent

El objeto Message#

Todas las operaciones de esta página responden con objetos Message, y los eventos de webhook llevan uno en data.message.

CampoTipoDescripción
idintegerEl ID del mensaje. Úsalo para obtener o cancelar el mensaje.
statusstringEn qué punto está ahora. Consulta Estados.
fromobjectEl número que envía: id, phone (sus dígitos con el código de país, sin +) y platform (whatsapp o telegram).
toobjectEl destinatario: phone (con +), group o username, y contact_id, el contacto guardado al que se envió. Un número de teléfono o un grupo al que se envía por primera vez se guarda como contacto. (contact_id solo es null para un mensaje de prueba a un número que no está guardado).
textstringEl texto o el pie de foto; "" cuando no hay.
mediaobject or nulltype (image o file), url y file_name (null para imágenes). La url puede ser un enlace que solo funciona durante un tiempo limitado; no la guardes.
send_atstring or nullLa hora para la que está programado, o null para "ahora".
expires_atstring or nullLa hora a partir de la cual ya no se envía, o null.
created_atstringCuándo se creó.
sent_atstring or nullCuándo salió, una vez que ha salido (sent, delivered o read).
errorobject or nullPara un mensaje failed o expired, message indica el motivo, como File exceeds WhatsApp size limit (16MB max). null en los demás casos.

Estados#

EstadoSignificado
queuedEsperando a salir.
scheduledEsperando a su send_at.
sentEnviado desde tu número (un tic en WhatsApp).
deliveredEntregado en el teléfono del destinatario (dos tics).
readLeído por el destinatario (tics azules).
failedNo se pudo enviar; error.message indica el motivo.
cancelledCancelado antes de salir: con Cancelar un mensaje, desde el panel o porque se quitó su número o su campaña.
expiredNo se envió antes de su expires_at.

queued y scheduled se pueden cancelar. sent aún puede pasar a delivered y después a read. failed, cancelled, expired y read son definitivos. Los webhooks informan de sent, failed, cancelled y expired; para ver delivered y read, obtén el mensaje.

Destinatarios#

to acepta exactamente uno de estos campos:

CampoEjemploQuién lo recibe
phone{"phone": "+919876543210"}Un número de teléfono en formato internacional con + (E.164). Se permiten espacios y guiones.
group{"group": "Bakery staff"}Un grupo de WhatsApp en el que está el número que envía, por su nombre exacto (hasta 200 caracteres).
contact_id{"contact_id": 991}Un contacto guardado en el espacio de trabajo, por su ID (por ejemplo, el contact_id de un mensaje anterior). Se envía al número o al grupo de ese contacto.
username{"username": "@asha_bakes"}Un usuario de Telegram: @name, un enlace t.me/name, o tg: y un ID de usuario. Solo desde una cuenta de Telegram, así que indica una como from (o ten exactamente una conectada).

Los envíos a grupos y a Telegram necesitan que el número que envía esté en ese grupo o pueda llegar a ese usuario; los problemas ahí aparecen como un mensaje failed, no como un error al enviar.

Medios#

media envía una imagen o un archivo, con text como pie de foto. Indica exactamente uno de url o media_id:

media.urlstringurl o media_id

Un enlace público http o https al archivo, de hasta 1.000 caracteres. Los enlaces a direcciones privadas o locales se rechazan con invalid_media_url.

media.media_idintegerurl o media_id

Un archivo del Directorio de medios del espacio de trabajo, de Subir un archivo. Sube un archivo una vez y envíalo tantas veces como quieras.

media.typestringopcional

image o file. Si lo omites, un enlace que termina en .jpg, .jpeg, .png, .gif o .webp se envía como imagen y cualquier otro como archivo; un media_id usa el tipo con el que se subió el archivo.

media.file_namestringopcional

Para un file: el nombre que ve el destinatario, como invoice-1042.pdf (hasta 100 caracteres). Si lo omites, el nombre se toma del enlace o del archivo subido. Su extensión decide cómo se envía el archivo, así que incluye una. Se ignora para las imágenes.

{
  "to": { "phone": "+919876543210" },
  "text": "Your invoice for order #1042",
  "media": { "url": "https://example.com/invoices/1042.pdf", "type": "file", "file_name": "invoice-1042.pdf" }
}

El archivo se descarga cuando se envía el mensaje, no cuando llamas a la API, así que un enlace roto o un archivo demasiado grande aparece como un mensaje failed con el motivo en error.message. Las reglas de tamaño y tipo son las de v1: consulta Enviar imágenes y archivos.

Programación y caducidad#

  • send_at programa el mensaje. Queda en scheduled hasta entonces y se puede cancelar hasta que sale. Escribe la hora con su desfase horario, así 2026-10-20T09:30:00+05:30 significa las 09:30 en la India. Para mensajes que se repiten, usa recordatorios.
  • expires_at es un plazo límite: si el mensaje no ha salido para entonces (por ejemplo, porque el número tenía la sesión cerrada), pasa a expired en lugar de salir tarde. Úsalo para códigos de un solo uso, horarios de entrega y ofertas. Un proceso en segundo plano lo marca como caducado al menos 30 segundos después del plazo, así que no confíes en plazos de menos de un minuto.
  • Ambos deben estar en el futuro, y expires_at debe ser posterior a send_at.

Relacionado#

  • Webhooks: recibe message.sent y message.failed en lugar de consultar una y otra vez.
  • Medios: sube archivos para enviarlos por media_id.
  • Números: el id que se usa como from.
  • Errores