Autenticación
Todos los endpoints requieren un Bearer Token en el header Authorization. Cada conexión de WhatsApp tiene su propio token de 48 caracteres.
Authorization: Bearer <TOKEN>Gestión de tokens
Los tokens se gestionan desde API Externa en la plataforma (solo administradores):
| Acción | Descripción |
|---|---|
| Generar | Crea un token de 48 caracteres alfanuméricos único por conexión |
| Rotar | Genera un nuevo token y reemplaza el anterior (el anterior deja de funcionar) |
| Eliminar | Revoca el token — la API deja de aceptar peticiones para esa conexión |
Error de autenticación 401
{ "error": "Acceso no permitido" }Base URL
La URL base de la API se deriva de la URL de su deployment de Convex, reemplazando .convex.cloud por .convex.site:
https://<su-deployment>.convex.sitePuede encontrar su URL exacta en la sección API Externa dentro de la plataforma.
Envía un mensaje de WhatsApp a un número. Si el contacto o ticket no existen, se crean automáticamente.
Parámetros
Content-Type: application/json o multipart/form-data (para archivos)
| Parámetro | Tipo | Descripción | |
|---|---|---|---|
| number | string | requerido | Número de teléfono destino (se normaliza automáticamente) |
| body | string | opcional | Texto del mensaje |
| mediaUrl | string | opcional | URL pública del archivo a enviar |
| mediaType | string | opcional | "image", "video", "audio" o "document" |
| mediaMime | string | opcional | MIME type del archivo (ej: "image/jpeg") |
| mediaKey | string | opcional | Key de S3 si el archivo ya está almacenado |
| closeTicket | boolean | opcional | Si true, cierra el ticket después de enviar |
Ejemplo
curl -X POST 'https://<deployment>.convex.site/api/messages/send' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <TOKEN>' \ -d '{ "number": "50688881234", "body": "Hola, su pedido está listo para recoger." }'
Ejemplo con archivo
curl -X POST 'https://<deployment>.convex.site/api/messages/send' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <TOKEN>' \ -d '{ "number": "50688881234", "body": "Aquí está su factura", "mediaUrl": "https://ejemplo.com/factura.pdf", "mediaType": "document", "mediaMime": "application/pdf" }'
Respuesta exitosa 200
{
"success": true,
"mensagem": "Mensaje enviado",
"ticketId": "<ticket-id>",
"messageId": "<message-id>"
}Errores 400
// Falta número { "error": "O número é obrigatório" } // JSON malformado { "error": "Invalid JSON" }
Obtiene los detalles de un ticket incluyendo su dataWebhook.
Ejemplo
curl 'https://<deployment>.convex.site/api/tickets/<ticketId>' \ -H 'Authorization: Bearer <TOKEN>'
Respuesta exitosa 200
{
"success": true,
"ticket": {
"id": "<ticket-id>",
"status": "open",
"contactId": "<contact-id>",
"whatsappId": "<whatsapp-id>",
"companyId": "<company-id>",
"dataWebhook": {},
"lastMessage": "Hola, necesito ayuda",
"updatedAt": 1714400000000
}
}Error 404
{ "error": "Ticket não encontrado" }Actualiza el estado y/o la cola de un ticket.
Parámetros
| Parámetro | Tipo | Descripción | |
|---|---|---|---|
| status | string | opcional* | "open", "pending" o "closed" |
| queueId | string | null | opcional* | ID de la cola destino, o null para quitar |
status o queueId es requerido. Al cerrar un ticket se limpian automáticamente integraciones, bots y flows activos.Ejemplo — cerrar ticket
curl -X PATCH 'https://<deployment>.convex.site/api/tickets/<ticketId>' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <TOKEN>' \ -d '{ "status": "closed" }'
Respuesta exitosa 200
{
"success": true,
"message": "ticket atualizado",
"ticket": { /* ticket actualizado */ }
}Lista los mensajes más recientes de un ticket.
Query parameters
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
| limit | number | 3 | Cantidad de mensajes a retornar |
Ejemplo
curl 'https://<deployment>.convex.site/api/tickets/<ticketId>/recent-messages?limit=5' \ -H 'Authorization: Bearer <TOKEN>'
Respuesta exitosa 200
{
"success": true,
"messages": [
{
"body": "Hola, necesito ayuda",
"fromMe": false,
"createdAt": "2026-04-29T10:00:00.000Z"
}
]
}Lista tickets pendientes que llevan cierto tiempo sin actividad. Útil para jobs de follow-up automático.
Query parameters
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
| minutes | number | 40 | Minutos de inactividad (alias: inactivityMinutes) |
| limit | number | 10 | Máximo de resultados (tope: 100) |
Ejemplo
curl 'https://<deployment>.convex.site/api/tickets/inactive-pending?minutes=30&limit=20' \ -H 'Authorization: Bearer <TOKEN>'
Respuesta exitosa 200
{
"success": true,
"tickets": [
{
"ticketId": "<id>",
"updatedAt": "2026-04-29T09:20:00.000Z",
"queueId": "<queue-id>",
"contactNumber": "50688881234",
"contactName": "María González",
"dataWebhook": {}
}
]
}Lista tickets cuyo estado de follow-up en dataWebhook puede limpiarse por antigüedad.
Query parameters
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
| minutes | number | 60 | Antigüedad en minutos (alias: ageMinutes) |
| limit | number | 100 | Máximo de resultados (tope: 250) |
Ejemplo
curl 'https://<deployment>.convex.site/api/tickets/stale-follow-up?minutes=120' \ -H 'Authorization: Bearer <TOKEN>'
Respuesta exitosa 200
{
"success": true,
"tickets": [
{
"ticketId": "<id>",
"staleAt": "2026-04-29T08:00:00.000Z"
}
]
}Hace un deep merge de JSON personalizado en el campo dataWebhook del ticket. Ideal para almacenar metadata de integraciones externas.
Parámetros
El body es cualquier objeto JSON válido. Se hace merge profundo con el dataWebhook existente.
dataWebhook resultante. Los arrays se reemplazan (no se concatenan). Keys con valor null eliminan esa key.Ejemplo
curl -X PATCH 'https://<deployment>.convex.site/api/tickets/<ticketId>/data-webhook' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <TOKEN>' \ -d '{ "orderId": "ORD-4521", "status": "shipped", "tracking": "CR123456789" }'
Respuesta exitosa 200
{
"success": true,
"message": "dataWebhook atualizado",
"ticketId": "<ticket-id>",
"dataWebhook": {
"orderId": "ORD-4521",
"status": "shipped",
"tracking": "CR123456789"
}
}Agrega etiquetas a un ticket. Resuelve por nombre (case-insensitive) o por ID.
Parámetros
| Parámetro | Tipo | Descripción | |
|---|---|---|---|
| ticketId | string | requerido | ID del ticket |
| tags | array | requerido | Array de objetos tag (no vacío) |
| tags[].name | string | opcional* | Nombre del tag (case-insensitive) |
| tags[].id | string | opcional* | ID del tag |
name o id. La operación es idempotente — agregar un tag que ya existe no genera error.Ejemplo
curl -X POST 'https://<deployment>.convex.site/api/tags/sync-external' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <TOKEN>' \ -d '{ "ticketId": "<ticket-id>", "tags": [ { "name": "VIP" }, { "name": "Urgente" } ] }'
Respuesta exitosa 200
{
"success": true,
"message": "Tags sincronizados",
"ticketId": "<ticket-id>",
"tagsAdded": ["<tag-id-1>", "<tag-id-2>"]
}Errores 400 404
// Faltan campos { "error": "ticketId e tags são obrigatórios" } // Tag no encontrada { "error": "Tag não encontrada: VIP" }
Lista productos del catálogo con paginación. Este endpoint debe ser implementado por conectores custom siguiendo la especificación BYOEcom.
Query parameters
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
| offset | number | 0 | Posición inicial (mínimo 0) |
| limit | number | 20 | Cantidad de productos (1-100) |
Headers requeridos
Authorization: Bearer <API_KEY>Respuesta exitosa 200
{
"status": "success",
"data": [
{
"id": "prod_001",
"name": "Camiseta Básica",
"description": "Camiseta 100% algodón",
"price": 15.99,
"currency": "USD",
"imageUrl": "https://ejemplo.com/img/camiseta.jpg",
"category": "Ropa",
"variant": "Talla M, Azul",
"available": true
}
],
"error": null
}Obtiene un producto específico por su identificador único.
Respuesta exitosa 200
{
"status": "success",
"data": {
"id": "prod_001",
"name": "Camiseta Básica",
"description": "Camiseta 100% algodón, disponible en varios colores",
"price": 15.99,
"currency": "USD",
"imageUrl": "https://ejemplo.com/img/camiseta.jpg",
"category": "Ropa",
"variant": "Talla M, Azul",
"available": true
},
"error": null
}No encontrado 404
{
"status": "error",
"data": null,
"error": "Product not found"
}Crea una nueva orden en el ecommerce con los productos seleccionados por el cliente.
Parámetros (body JSON)
| Parámetro | Tipo | Descripción | |
|---|---|---|---|
| lineItems | array | requerido | 1-100 items, cada uno con productId y quantity |
| customerName | string | requerido | Nombre del cliente |
| customerContact | string | requerido | Teléfono o email del cliente |
| deliveryAddress | string | requerido | Dirección de entrega |
| paymentMethod | string | requerido | Método de pago preferido |
| discountCode | string | opcional | Código de descuento a aplicar |
Ejemplo
{
"lineItems": [
{ "productId": "prod_001", "quantity": 2 },
{ "productId": "prod_005", "quantity": 1 }
],
"customerName": "María González",
"customerContact": "+50688881234",
"deliveryAddress": "San José, Escazú, 200m norte del parque",
"paymentMethod": "sinpe_movil"
}Respuesta exitosa 200
{
"status": "success",
"data": {
"orderId": "ORD-2026-0042",
"status": "pending",
"total": 47.97,
"currency": "USD",
"createdAt": "2026-05-11T14:30:00Z"
},
"error": null
}Obtiene los detalles y estado actual de una orden existente.
Respuesta exitosa 200
{
"status": "success",
"data": {
"orderId": "ORD-2026-0042",
"status": "shipped",
"lineItems": [
{ "name": "Camiseta Básica", "quantity": 2, "price": 15.99 }
],
"total": 47.97,
"currency": "USD",
"createdAt": "2026-05-11T14:30:00Z"
},
"error": null
}Endpoint de PolpoChat que recibe eventos de su ecommerce (cambios de estado de orden, etc.). Cuando se recibe un evento válido, PolpoChat envía automáticamente una notificación al cliente por WhatsApp.
Headers requeridos
| Header | Descripción |
|---|---|
| X-Webhook-Secret | Secret único por empresa (se genera en la configuración de BYOEcom) |
| Content-Type | application/json |
Payload
{
"event": "order_status_changed",
"orderId": "ORD-2026-0042",
"newStatus": "shipped",
"timestamp": "2026-05-11T16:00:00Z"
}Eventos soportados
| Evento | Descripción |
|---|---|
| order_status_changed | El estado de una orden cambió |
| order_created | Se creó una nueva orden externamente |
| order_cancelled | Una orden fue cancelada |
Respuesta exitosa 200
{ "success": true, "message": "Event processed" }Error de autenticación 401
{ "error": "Invalid webhook secret" }