> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mka1.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversacional multicanal

> Demuestra que los canales de WhatsApp y web comparten una conversación almacenada de la API MKA1 con registros de auditoría JSONL unificados.

Utiliza esta guía cuando necesites mostrar que el mismo usuario final puede interactuar a través de dos canales y que ambos canales producen registros unificados y auditables.
El patrón a continuación utiliza un webhook de WhatsApp como canal uno y una aplicación web como canal dos.
Ambos entornos envían el mismo ID de usuario final `X-On-Behalf-Of` y ambos escriben eventos de auditoría JSONL asociados al mismo `response_id` y `conversation_id`.

<Warning>
  Utiliza la misma clave de API MKA1 en ambos entornos.
  Dos claves de API diferentes representan dos usuarios diferentes de la API MKA1.
  Usar el mismo valor de `X-On-Behalf-Of` no es suficiente si el entorno de WhatsApp y el entorno web se autentican con claves de API diferentes.
  Para este flujo de evidencia, ambos canales deben compartir la misma clave de API MKA1 y el mismo ID de usuario final `X-On-Behalf-Of`.
</Warning>

Para el patrón básico de solicitud, consulta [generar una respuesta](/es/docs/generate-a-response).
Si necesitas crear o gestionar un ID de conversación reutilizable por usuario final, consulta [gestionar conversaciones](/es/docs/conversations).

## Arquitectura

Mensaje del usuario final en WhatsApp
-> El webhook de WhatsApp recibe el mensaje
-> tu servidor llama a `mka1.llm.responses.create` con `store: true`
-> tu servidor registra el objeto de respuesta completo almacenado y el `response_id`
-> tu servidor envía la respuesta de vuelta a WhatsApp
-> tu aplicación web solicita el mismo `response_id` desde tu backend
-> tu backend llama a `mka1.llm.responses.get`
-> tu backend registra la recuperación web contra el mismo `response_id` y `conversation_id`

<Note>
  Mantén tu clave de API en el servidor. El navegador debe llamar a tu ruta backend, no directamente a la API MKA1.
</Note>

## Usa un solo ID de usuario final en ambos canales

El valor de `X-On-Behalf-Of` es la clave que une los canales a nivel de usuario final.
Utiliza el mismo ID de usuario final estable en ambos entornos.
También debes usar la misma clave de API MKA1 compartida en ambos entornos.

Ejemplos:

* El webhook de WhatsApp asigna un número de teléfono a `user_123`.
* La sesión web para la misma persona también se resuelve como `user_123`.
* Ambos entornos se autentican con la misma clave de API MKA1.

Si el valor de `X-On-Behalf-Of` cambia entre canales, los registros ya no son auditables como una sola conversación de usuario final.
Si la clave de API cambia entre canales, las solicitudes pertenecen a diferentes usuarios de la API MKA1 incluso cuando `X-On-Behalf-Of` coincide.

## Entorno WhatsApp: almacena la respuesta y regístrala

Comienza con un único wrapper SDK compartido que ambos entornos utilicen.
Esto mantiene la autenticación, la creación de conversaciones, la recuperación y creación de respuestas consistente entre canales.

```ts theme={null}
import { SDK } from "@meetkai/mka1";

const getAuthHeaders = (userId: string) => {
  return {
    "X-On-Behalf-Of": userId,
  };
};

const mka1 = new SDK({
  bearerAuth: `Bearer ${process.env.MKA1_API_KEY}`,
});

const createConversation = async ({ userId }: { userId: string }) => {
  return await mka1.llm.conversations.create({}, { headers: getAuthHeaders(userId) });
};

const getResponse = async ({
  userId,
  responseId,
}: {
  userId: string;
  responseId: string;
}) => {
  return await mka1.llm.responses.get({ responseId }, { headers: getAuthHeaders(userId) });
};

const sendUserMessage = async ({
  conversationId,
  userId,
  message,
  previousResponseId,
  channel,
}: {
  conversationId: string;
  userId: string;
  message: string;
  previousResponseId?: string;
  channel: "whatsapp" | "webapp";
}) => {
  return await mka1.llm.responses.create(
    {
      model: "meetkai:functionary-pt",
      input: message,
      conversation: conversationId,
      previousResponseId,
      store: true,
      stream: false,
      metadata: {
        channel,
      },
    },
    {
      headers: getAuthHeaders(userId),
    }
  );
};
```

Luego utiliza ese wrapper en tu webhook de WhatsApp.
Crea la conversación una vez para el usuario final, reutilízala en cada turno y captura el evento de auditoría de la respuesta almacenada.

```ts theme={null}
import { Router, Request, Response } from "express";
import { OutputMessage } from "@meetkai/mka1/models/components";
import { sendTextMessage } from "./whatsapp";
import { createConversation, sendUserMessage } from "./mka1";

const router = Router();

router.post("/webhook", async (req: Request, res: Response) => {
  res.sendStatus(200);

  const entry = req.body.entry?.[0];
  const changes = entry?.changes?.[0];
  const value = changes?.value;
  const message = value?.messages?.[0];

  if (!message || message.type !== "text") return;

  const senderPhone = message.from;
  const incomingText = message.text.body.trim();
  const userId = await getEndUserIdForPhone(senderPhone); // e.g. "123"

  let conversationId = await getConversationIdForEndUser(userId);

  if (!conversationId) {
    const conversation = await createConversation({ userId });
    conversationId = conversation.id;
    await saveConversationIdForEndUser(userId, conversationId);
  }

  const previousResponseId = await getPreviousResponseIdForConversation(conversationId);

  const response = await sendUserMessage({
    conversationId,
    userId,
    message: incomingText,
    previousResponseId,
    channel: "whatsapp",
  });

  const assistantMessage = response.output.find(
    (item): item is OutputMessage => item.type === "message" && item.role === "assistant"
  );
  const output = assistantMessage?.content.map((item) => item.text).join("") ?? "";

  const auditEvent = {
    timestamp: new Date().toISOString(),
    channel: "whatsapp",
    action: "responses.create",
    end_user_id: userId,
    response_id: response.id,
    conversation_id: response.conversation.id,
    previous_response_id: response.previousResponseId,
    full_response: response,
  };

  await savePreviousResponseIdForConversation(conversationId, response.id);

  await sendTextMessage(
    {
      accessToken: process.env.WHATSAPP_TOKEN!,
      phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
    },
    senderPhone,
    output
  );
});
```

Este es el punto de evidencia crítico para el canal uno:

* WhatsApp es el canal de entrada.
* La solicitud se almacena con `store: true`.
* El objeto de respuesta completo se registra en un formato abierto.
* El registro incluye `response_id`, `conversation_id` y `end_user_id`.

## Entorno web: recupera la misma respuesta almacenada

La aplicación web debe llamar a tu propio backend.
Ese backend puede recuperar la misma respuesta con `mka1.llm.responses.get` y registrar la recuperación como un evento de segundo canal.

```ts theme={null}
import express from "express";
import { getResponse } from "./mka1";

const app = express();

app.get("/api/multichannel/:responseId", async (req, res) => {
  const responseId = req.params.responseId;
  const userId = await getEndUserIdForWebSession(req); // mismo valor, por ejemplo "123"
  const response = await getResponse({ userId, responseId });

  const auditEvent = {
    timestamp: new Date().toISOString(),
    channel: "webapp",
    action: "responses.get",
    end_user_id: userId,
    response_id: response.id,
    conversation_id: response.conversation.id,
    previous_response_id: response.previousResponseId,
    full_response: response,
  };

  res.json({
    response_id: response.id,
    conversation_id: response.conversation.id,
    response,
  });
});
```

Llamada mínima desde el navegador:

```ts theme={null}
const record = await fetch(`/api/multichannel/${responseId}`).then((res) => res.json());

console.log(record.response_id);
console.log(record.conversation_id);
console.log(record.response);
```

Este es el punto de evidencia crítico para el canal dos:

* La aplicación web es un canal separado de WhatsApp.
* Recupera la misma respuesta MKA1 almacenada por `response_id`.
* Registra esa recuperación en el mismo flujo de auditoría JSONL.
* El objeto recuperado sigue manteniendo el mismo enlace de usuario final y conversación.

## Exportación de auditoría unificada en formato abierto

Ejemplo de exportación:

```json theme={null}
{"timestamp":"2026-03-30T19:10:14.000Z","channel":"whatsapp","action":"responses.create","end_user_id":"123","response_id":"resp_abc123","conversation_id":"conv_abc123","previous_response_id":null,"full_response":{"id":"resp_abc123","model":"meetkai:functionary-pt","store":true,"status":"completed","conversation":{"id":"conv_abc123"},"metadata":{"channel":"whatsapp"}}}
{"timestamp":"2026-03-30T19:11:02.000Z","channel":"webapp","action":"responses.get","end_user_id":"123","response_id":"resp_abc123","conversation_id":"conv_abc123","previous_response_id":null,"full_response":{"id":"resp_abc123","model":"meetkai:functionary-pt","store":true,"status":"completed","conversation":{"id":"conv_abc123"},"metadata":{"channel":"whatsapp"}}}
```

## Requisito de autenticación para registros compartidos

Para esta demostración multicanal, todo lo siguiente debe coincidir entre los entornos de WhatsApp y web:

* la misma clave de API MKA1
* el mismo ID de usuario final `X-On-Behalf-Of`
* el mismo `response_id` almacenado
* el mismo `conversation_id` cuando uses conversaciones

Si cualquiera de los entornos utiliza una clave de API diferente, ya no estarás mostrando un contexto de usuario de API MKA1 compartido entre canales.
