> ## 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

> Demonstre canais WhatsApp e web compartilhando uma mesma conversa armazenada da API MKA1 com logs de auditoria JSONL unificados.

Use este guia quando precisar mostrar que o mesmo usuário final pode interagir em dois canais e que ambos produzem logs unificados e auditáveis.
O padrão abaixo utiliza um webhook do WhatsApp como canal um e um aplicativo web como canal dois.
Ambos os sistemas enviam o mesmo ID de usuário final `X-On-Behalf-Of` e ambos gravam eventos de auditoria JSONL indexados pelo mesmo `response_id` e `conversation_id`.

<Warning>
  Use a mesma chave de API MKA1 em ambos os sistemas.
  Duas chaves de API diferentes representam dois usuários diferentes da API MKA1.
  Usar o mesmo valor de `X-On-Behalf-Of` não é suficiente se o sistema do WhatsApp e o sistema web autenticarem com chaves de API diferentes.
  Para este fluxo de evidência, ambos os canais devem compartilhar a mesma chave de API MKA1 e o mesmo ID de usuário final `X-On-Behalf-Of`.
</Warning>

Para o padrão básico de requisição, veja [gerar uma resposta](/pt/docs/generate-a-response).
Se precisar criar ou gerenciar um ID de conversa reutilizável por usuário final, veja [gerenciar conversas](/pt/docs/conversations).

## Arquitetura

Mensagem do usuário final no WhatsApp
-> Webhook do WhatsApp recebe a mensagem
-> seu servidor chama `mka1.llm.responses.create` com `store: true`
-> seu servidor registra o objeto de resposta completo armazenado e o `response_id`
-> seu servidor envia a resposta de volta ao WhatsApp
-> seu aplicativo web solicita o mesmo `response_id` do seu backend
-> seu backend chama `mka1.llm.responses.get`
-> seu backend registra a recuperação web com o mesmo `response_id` e `conversation_id`

<Note>
  Mantenha sua chave de API no servidor. O navegador deve chamar sua rota backend, não a API MKA1 diretamente.
</Note>

## Use um único ID de usuário final em ambos os canais

O valor de `X-On-Behalf-Of` é a chave que conecta os canais no nível do usuário final.
Use o mesmo ID de usuário final estável em ambos os sistemas.
Você também deve usar a mesma chave de API MKA1 compartilhada em ambos.

Exemplos:

* O webhook do WhatsApp mapeia um número de telefone para `user_123`.
* A sessão web para a mesma pessoa também resolve para `user_123`.
* Ambos os sistemas autenticam com a mesma chave de API MKA1.

Se o valor de `X-On-Behalf-Of` mudar entre os canais, os logs deixam de ser auditáveis como uma única conversa de usuário final.
Se a chave de API mudar entre os canais, as requisições pertencem a usuários diferentes da API MKA1, mesmo quando `X-On-Behalf-Of` coincide.

## Sistema WhatsApp: armazene e registre a resposta

Comece com um wrapper SDK compartilhado que ambos os sistemas utilizam.
Isso mantém autenticação, criação de conversa, recuperação e criação de resposta consistentes entre os canais.

```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),
    }
  );
};
```

Depois, utilize esse wrapper no seu webhook do WhatsApp.
Crie a conversa uma vez para o usuário final, reutilize-a entre as interações e capture o evento de auditoria a partir da resposta armazenada.

```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 é o ponto de evidência crítico para o canal um:

* O WhatsApp é o canal de entrada.
* A requisição é armazenada com `store: true`.
* O objeto de resposta completo é registrado em formato aberto.
* O log inclui `response_id`, `conversation_id` e `end_user_id`.

## Sistema web: recupere a mesma resposta armazenada

O aplicativo web deve chamar seu próprio backend.
Esse backend pode recuperar a mesma resposta com `mka1.llm.responses.get` e registrar a recuperação como um evento do 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); // mesmo valor, ex: "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,
  });
});
```

Chamada mínima do 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 é o ponto de evidência crítico para o canal dois:

* O aplicativo web é um canal separado do WhatsApp.
* Ele recupera a mesma resposta MKA1 armazenada pelo `response_id`.
* Ele registra essa recuperação no mesmo fluxo de auditoria JSONL.
* O objeto recuperado ainda mantém o mesmo vínculo de usuário final e conversa.

## Exportação unificada de auditoria em formato aberto

Exemplo de exportação:

```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 autenticação para logs compartilhados

Para esta demonstração multicanal, todos os itens a seguir devem coincidir entre os sistemas WhatsApp e web:

* a mesma chave de API MKA1
* o mesmo ID de usuário final `X-On-Behalf-Of`
* o mesmo `response_id` armazenado
* o mesmo `conversation_id` quando você usa conversas

Se qualquer um dos sistemas usar uma chave de API diferente, você não estará mais demonstrando um contexto de usuário compartilhado da API MKA1 entre canais.
