Skip to content

Integração com a API NestJS

Contrato HTTP entre o contrasync-whatsapp-bot (Lambda) e o módulo WhatsappModule no contrasync-nest-api.


Visão geral

Toda a persistência fica no nest-api. O bot é stateless e consome endpoints HTTP em /whatsapp/bot/*. Esses endpoints estão atrás do BotApiKeyGuard: só aceitam requisições com o header x-bot-api-key correspondendo a process.env.BOT_API_KEY do servidor.

LadoVariávelConteúdo
Lambda (contrasync-whatsapp-bot)BOT_API_KEYHeader enviado em cada request
API (contrasync-nest-api)BOT_API_KEYValor esperado pelo BotApiKeyGuard

Os dois precisam ter o mesmo valor. Gerar com:

bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Endpoints

GET /whatsapp/bot/account

Busca a WhatsappAccount pelo número.

Query: ?whatsappNumber=5511999998888

Resposta 200 (vinculada):

json
{
  "id": "uuid",
  "userId": "uuid",
  "whatsappNumber": "5511999998888",
  "defaultCompanyId": "uuid-or-null",
  "defaultCompanyName": "Acme Consultoria",
  "userName": "João Silva",
  "userEmail": "[email protected]"
}

Resposta 200 (não vinculada): null


POST /whatsapp/bot/otp/issue

Resolve o usuário por email/CPF/CNPJ e envia OTP por email.

Body:

json
{ "identifier": "[email protected]", "whatsappNumber": "5511999998888" }

Resposta 201:

json
{ "emailSentTo": "jo***@acme.com", "expiresAt": "2026-05-12T10:30:00.000Z" }

Erros possíveis:

  • 404: identifier inválido ou não encontrado
  • 409: CNPJ com mais de um usuário ativo

A API resolve identifier na seguinte ordem:

  1. Se contém @: lookup por User.email
  2. Se tem 11 dígitos: lookup por User.cpf
  3. Se tem 14 dígitos: lookup por Company.document. Se exatamente 1 usuário ativo → usa. Mais que 1 → 409.

POST /whatsapp/bot/otp/verify

Valida OTP, marca como consumido, cria/atualiza a WhatsappAccount e retorna a lista de empresas do usuário.

Body:

json
{ "whatsappNumber": "5511999998888", "code": "123456" }

Resposta 201:

json
{
  "account": {
    "id": "uuid",
    "userId": "uuid",
    "whatsappNumber": "5511999998888",
    "defaultCompanyId": null
  },
  "companies": [
    { "id": "uuid", "name": "Acme", "document": "12345678000190", "role": "provider" },
    { "id": "uuid", "name": "Beta", "document": "98765432000111", "role": "borrower" }
  ]
}

Se o usuário tem exatamente 1 empresa, account.defaultCompanyId já vem preenchido. Se tem múltiplas, fica null até a escolha via switch-company.

Erros possíveis:

  • 404: nenhum OTP pendente ou expirado
  • 403: código inválido (tentativas incrementadas) ou limite (5) atingido

GET /whatsapp/bot/companies

Lista empresas disponíveis para um número já vinculado.

Query: ?whatsappNumber=5511999998888

Resposta 200:

json
[
  { "id": "uuid", "name": "Acme", "document": "12345678000190", "role": "provider" }
]

Erros:

  • 404: número não vinculado a nenhuma conta

POST /whatsapp/bot/switch-company

Troca a empresa padrão da conta.

Body:

json
{ "whatsappNumber": "5511999998888", "companyId": "uuid" }

Resposta 201: WhatsappAccount atualizada.

Erros:

  • 404: número não vinculado
  • 403: usuário não pertence à empresa selecionada

POST /whatsapp/bot/idempotency

Registra o messageId processado. Devolve duplicate: true se já existia.

Body:

json
{ "messageId": "MSG-001", "whatsappNumber": "5511999998888" }

Resposta 201:

json
{ "duplicate": false, "processedAt": "2026-05-12T10:30:00.000Z" }

Se duplicado: { "duplicate": true, "processedAt": "<ISO da primeira gravação>" }.


Tratamento de erros no ContrasyncApiClient

O cliente HTTP do bot (src/adapters/contrasync-api/contrasync-api.client.ts) traduz cada erro do upstream:

typescript
private fail(operation: string, error: unknown): never {
  const status = error instanceof AxiosError ? error.response?.status : undefined

  if (status && status >= 400 && status < 500) {
    throw new ValidationError(upstreamMessage)
  }

  throw new BotError('Falha ao comunicar com a API do Contrasync', 502)
}
  • 4xx: ValidationError carregando a mensagem do upstream. O BotRouterService captura e devolve essa mensagem ao usuário via Z-API.
  • 5xx, timeout, network: BotError 502 com mensagem genérica. Sobe pro boundary, vira HTTP 502, fica visível no log mas mascarado para o usuário.

Mensagens devolvidas pela API NestJS (ex.: "Nenhum usuário encontrado para esse email.") já são adequadas para mostrar diretamente ao usuário do WhatsApp.


Recursos relacionados

  • Schema Prisma: prisma/schema/whatsapp.prisma no nest-api, WhatsappAccount, WhatsappOtp, WhatsappMessageIdempotency
  • Guard: src/common/guards/bot-api-key.guard.ts no nest-api
  • Module: src/modules/whatsapp/whatsapp.module.ts no nest-api
  • Fluxo de connect: ./connect-flow.md

Documento atualizado em Maio 2026