Saltar para o conteúdo principal
Use este guia quando precisar mostrar que o mesmo end user pode interagir em dois canais e que ambos os canais produzem logs unificados e auditáveis. O padrão abaixo usa um webhook do WhatsApp como canal um e uma aplicação web como canal dois. As duas integrações enviam o mesmo ID de end user em X-On-Behalf-Of e ambas gravam eventos de auditoria JSONL indexados pelo mesmo response_id e conversation_id.
Use a mesma API key da MKA1 API nas duas integrações. Duas API keys diferentes representam dois usuários diferentes da MKA1 API. Usar o mesmo valor de X-On-Behalf-Of não é suficiente se a integração do WhatsApp e a integração web se autenticarem com API keys diferentes. Para este fluxo de evidência, os dois canais precisam compartilhar a mesma API key da MKA1 API e o mesmo ID de end user em X-On-Behalf-Of.
Para o padrão base de requisição, veja gerar uma resposta. Se você precisar criar ou gerenciar um ID de conversa reutilizável por end user, veja gerenciar conversas.

Arquitetura

Mensagem do end user no WhatsApp -> webhook do WhatsApp recebe a mensagem -> seu servidor chama mka1.llm.responses.create com store: true -> seu servidor registra o objeto completo da resposta armazenada e o response_id -> seu servidor envia a resposta de volta para o WhatsApp -> sua aplicação web solicita o mesmo response_id ao seu backend -> seu backend chama mka1.llm.responses.get -> seu backend registra a recuperação web com o mesmo response_id e conversation_id
Mantenha sua API key no servidor. O navegador deve chamar a sua rota de backend, não a MKA1 API diretamente.

Use um ID de end user em ambos os canais

O valor de X-On-Behalf-Of é a chave que conecta os canais no nível do end user. Use o mesmo ID estável de end user nas duas integrações. Você também precisa usar a mesma API key compartilhada da MKA1 API nas duas integrações. Exemplos:
  • O webhook do WhatsApp mapeia um número de telefone para user_123.
  • A sessão da aplicação web da mesma pessoa também resolve para user_123.
  • As duas integrações se autenticam com a mesma API key da MKA1 API.
Se o valor de X-On-Behalf-Of mudar entre os canais, os logs deixam de ser auditáveis como uma única conversa de end user. Se a API key mudar entre os canais, as requisições passam a pertencer a usuários diferentes da MKA1 API, mesmo quando X-On-Behalf-Of coincide.

Integração do WhatsApp: armazene a resposta e registre o log

Comece com um wrapper compartilhado do SDK que as duas integrações usam. Isso mantém autenticação, criação de conversa, recuperação de resposta e criação de resposta consistentes entre os canais.
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 use esse wrapper no webhook do WhatsApp. Crie a conversa uma vez para o end user, reutilize-a entre os turnos e capture o evento de auditoria da resposta armazenada.
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 crítico de evidência para o canal um:
  • WhatsApp é o canal de entrada.
  • A requisição é armazenada com store: true.
  • O objeto completo da resposta é registrado em um formato aberto.
  • O log inclui response_id, conversation_id e end_user_id.

Integração web: recupere a mesma resposta armazenada

A aplicação web deve chamar o seu próprio backend. Esse backend pode recuperar a mesma resposta com mka1.llm.responses.get e registrar a recuperação como um segundo evento de canal.
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); // same value, e.g. "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 no navegador:
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 crítico de evidência para o canal dois:
  • A aplicação web é um canal separado do WhatsApp.
  • Ele recupera a mesma resposta armazenada da MKA1 por response_id.
  • Ele registra essa recuperação no mesmo fluxo de auditoria JSONL.
  • O objeto recuperado continua carregando o mesmo vínculo de end user e conversa.

Exportação de auditoria unificada em formato aberto

Exemplo de exportação:
{"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, tudo abaixo precisa coincidir entre as integrações de WhatsApp e web:
  • a mesma API key da MKA1 API
  • o mesmo ID de end user em X-On-Behalf-Of
  • o mesmo response_id armazenado
  • o mesmo conversation_id quando você usa conversas
Se qualquer integração usar uma API key diferente, você deixa de demonstrar um contexto compartilhado de um único usuário da MKA1 API entre canais.