Ir al contenido

Enviar mensajes

El endpoint principal del Canal API es POST /channel/message. Devuelve un 200 inmediato confirmando que el mensaje se encoló; la respuesta del bot llega después por webhook o por el stream SSE.

POST https://api.overtaker.online/api/v1/channel/message
Authorization: Bearer sk_live_YOUR_KEY
Content-Type: application/json
X-Overtaker-Version: 1
Idempotency-Key: msg-001 # opcional — recomendado, evita duplicados (TTL 24h)
{
"session_id": "user-123", // recomendado — identidad opaca de la sesión (≤200 chars)
"email": "cliente@empresa.com", // opcional — solo si lo conocés (se usa para el Lead)
"contact_name": "Café Aroma", // opcional — nombre legible del contacto (≤120 chars)
"message": "Hola, necesito ayuda", // requerido — texto (máx. 5000 chars). OPCIONAL en audio (se transcribe)
"message_type": "text", // opcional — ver tabla abajo (default: "text")
"media_base64": "<base64>", // opcional — archivo en base64
"media_mime_type": "image/jpeg" // obligatorio si hay media_base64
}

Respuesta:

{
"message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "received"
}

session_id es el identificador opaco de la sesión. El bot lo usa para reconocer al usuario y mantener contexto entre mensajes. Puede ser cualquier string de hasta 200 caracteres (UUID, ID interno, teléfono, lo que sea).

Qué envíasComportamiento
Solo session_idSesión anónima válida — el bot opera sin email del usuario.
Solo emailCompat histórica — el session_id se deriva del email.
Ambossession_id manda como identidad de sesión; email se preserva para identificar/crear el Lead.
// Mismo session_id = misma sesión, el bot recuerda el contexto
{"session_id": "user-123", "message": "Hola"}
{"session_id": "user-123", "message": "¿Qué me dijiste antes?"} // ✓ recuerda
// session_id distinto = sesión nueva, sin contexto previo
{"session_id": "user-456", "message": "Hola"} // conversación independiente

contact_name es opcional y le da al contacto un nombre legible en el panel (de lo contrario aparece como api:<session_id>, ilegible y no-buscable). Útil cuando ya conocés el nombre del cliente/empresa al iniciar la sesión.

AspectoComportamiento
Cuándo aplicaAl crear el contacto, y en mensajes posteriores mientras aún no tenga nombre.
Si ya tiene nombreNo se pisa — un nombre ya capturado (por vos o por el agente) tiene prioridad.
Ausente / vacíoNo-op, flujo idéntico al actual.
LargoSe recorta a 120 caracteres.
{ "session_id": "proyecto-42", "message": "Hola", "contact_name": "Café Aroma" }
// → el contacto queda como "Café Aroma" en el panel y en el evento contact.updated

Si necesitás capturar más datos del contacto durante la conversación (no solo el nombre), eso lo hace el agente vía protocolo — ver Completar datos estructurados.

message_typeFormatos soportadosmedia_mime_type (ejemplos)
textTexto plano (default)— (no lleva media)
imageJPEG, PNG, WebP, GIFimage/jpeg, image/png, image/webp, image/gif
videoMP4, 3GPPvideo/mp4
audioMP3, OGG, MP4 audio, WebMaudio/mpeg, audio/ogg, audio/mp4, audio/webm
documentPDF, Word, Excel, PowerPoint, TXTapplication/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document (.docx)

Para mensajes con media, codificá el archivo en base64 y declará su media_mime_type:

# Python
import base64, requests
with open("imagen.jpg", "rb") as f:
b64 = base64.b64encode(f.read()).decode()
requests.post(f"{BASE}/channel/message", headers=HEADERS, json={
"session_id": "user-123",
"message": "Mira esta imagen",
"message_type": "image",
"media_base64": b64,
"media_mime_type": "image/jpeg",
})
Node.js
const fs = require("fs");
const b64 = fs.readFileSync("./imagen.jpg").toString("base64");
await fetch(`${BASE}/channel/message`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
session_id: "user-123",
message: "Mira esta imagen",
message_type: "image",
media_base64: b64,
media_mime_type: "image/jpeg",
}),
});

Si tu request falla por timeout o red, podés reintentarlo con la misma clave de idempotencia y el mensaje no se procesa dos veces. Usá el header HTTP estándar Idempotency-Key (estilo Stripe / PayPal):

POST https://api.overtaker.online/api/v1/channel/message
Authorization: Bearer sk_live_YOUR_KEY
Content-Type: application/json
X-Overtaker-Version: 1
Idempotency-Key: mi-sistema-msg-id-20260527-001
{ "session_id": "user-123", "message": "Quiero cotizar" }
// Reintento con la misma key dentro de 24h → mismo message_id, sin reprocesar.
  • TTL: 24 horas.
  • Máximo: 200 caracteres (si supera → 400).

El bot puede decidir no responder. En ese caso no llega ningún evento message.response a tu webhook. Ocurre cuando el bot detecta un mensaje fuera de contexto, spam, o está en modo handoff activo.

POST /channel/message/stream es la variante streaming. Mismo body que /channel/message, pero la respuesta llega como Server-Sent Events — pensado para chats con efecto “typing” donde mostrás el texto a medida que se genera. Bucket write (60/min).

POST https://api.overtaker.online/api/v1/channel/message/stream
Authorization: Bearer sk_live_YOUR_KEY
Content-Type: application/json
X-Overtaker-Version: 1
Accept: text/event-stream
{ "session_id": "user-123", "message": "Cuéntame de sus planes" }

Response (text/event-stream):

event: token
data: {"text": "Tenemos tres "}
event: token
data: {"text": "planes principales: "}
event: token
data: {"text": "Starter, Pro y Enterprise..."}
event: done
data: {
"message_id": "uuid",
"conversation_id": "uuid",
"lead_id": "uuid o vacío",
"session_id": "user-123",
"full_text": "Tenemos tres planes principales: Starter, Pro y Enterprise...",
"quick_replies": ["Ver Starter", "Ver Pro"],
"action": "REPLY"
}
// En caso de error
event: error
data: {"detail": "Error interno procesando el mensaje"}
// Node.js — consumir el stream con fetch + ReadableStream
const res = await fetch(`${BASE}/channel/message/stream`, {
method: "POST",
headers: { ...HEADERS, Accept: "text/event-stream" },
body: JSON.stringify({ session_id: "user-123", message: "Hola" }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const blocks = buffer.split("\n\n"); // \n\n delimita un evento SSE
buffer = blocks.pop() || ""; // resto incompleto
for (const block of blocks) {
const event = block.match(/^event:\s*(.+)$/m)?.[1];
const data = block.match(/^data:\s*(.+)$/m)?.[1];
if (!event || !data) continue;
const payload = JSON.parse(data);
if (event === "token") process.stdout.write(payload.text);
if (event === "done") console.log("\n[FIN]", payload.message_id);
if (event === "error") console.error("[ERROR]", payload.detail);
}
}