Documentação / Envio de mensagens a grupos (API pública e dashboard)

Envio de mensagens a grupos (API pública e dashboard)

Entrar

Envio de mensagens a grupos (API pública e dashboard)

Este documento descreve como a plataforma envia mensagens de template para grupos do WhatsApp, pela API pública e pela área logada (dashboard), e quais regras de negócio se aplicam. O processamento de fila, worker e webhooks segue o fluxo geral em api-publica-fluxo-envio-mensagem.md e message-analisys/fluxo-envio-mensagens.md.

Conceito: destino = grupo

Um grupo é identificado pelo JID do WhatsApp terminado em @g.us (ex.: [email protected]). Na API, esse valor vai no campo groupId, em substituição de destinationNumber (não é permitido enviar os dois no mesmo request).

A validação central está em sendMessageSchema (packages/shared/src/validation.ts):

  • groupId: string não vazia, deve casar com …@g.us.
  • Exatamente um de destinationNumber ou groupId é obrigatório.

API pública

| Item | Valor | |------|--------| | Rota (Next.js App Router) | POST /api/v1/messages/send | | Autenticação | Header x-api-key (ou x-api-key-id) — ver getPublicApiKeyInfo / public-api-auth | | Handler compartilhado | handleSendWithKeyInfo em apps/fullstack/src/app/api/v1/messages/send/route.ts |

Exemplo mínimo de corpo (grupo):

{
  "templateId": "seu-template",
  "groupId": "[email protected]",
  "variables": { "name": "Equipe" }
}

A documentação voltada ao cliente (incluindo groupId e demais campos opcionais) está em apps/fullstack/public/llms/docs-api/06-send-messages.md. A base URL pública publicada (ex.: https://pilotstatus.online/v1/...) pode ser exposta via gateway que remapeia o prefixo; o código do app vive em /api/v1/....

Resposta: 202 com id, correlationId, status, createdAt, origin (nome da instância WhatsApp), como nos envios para número individual.


Dashboard

| Item | Valor | |------|--------| | Rota interna | POST /api/internal/messages/send | | Autenticação | Sessão do app (getAppSession) — usuário precisa estar logado no tenant | | Arquivo | apps/fullstack/src/app/api/internal/messages/send/route.ts |

Fluxo resumido:

  1. Valida o JSON com apiKeyId + templateId + (destinationNumber ou groupId) + variables opcionais, deliverAt / deliverUntil, marketingOptions opcional.
  2. Carrega a API key do mesmo tenant que a sessão; se não achar, 404.
  3. Chama ensureEnvironmentAccess (permissão do usuário para o ambiente/projeto daquela chave).
  4. Atualiza lastUsedAt da chave.
  5. Delega para o mesmo handleSendWithKeyInfo usado na API pública, passando sendContext: dashboardSession: true e allowLiveMarketingOnPilotInstance: true apenas se o e-mail do usuário for de administrador da plataforma (isAdminEmail).

Na UI da página de mensagens (apps/fullstack/src/spa/pages/Messages.tsx), o destino é um campo de texto. Se o valor (após trim) termina com @g.us, o front envia como groupId; caso contrário, como destinationNumber. Ou seja, o operador cola o JID do grupo no campo de destino, como faria com um telefone E.164.


Onde as duas entradas convergem

Tanto a API pública quanto o dashboard usam handleSendWithKeyInfo. A fila, gravação da mensagem e worker são as mesmas; a diferença principal entre origens está no contexto usado para a trava de template MARKETING na instância “Pilot Status” padrão (veja a tabela abaixo).

O destino efetivo enviado ao MessageService.send é o JID do grupo (campo interno destinationNumber no serviço guarda o destino, seja E.164 ou JID de grupo — ver comentário em message.service sobre “E.164 digits or group JID”).


Regras de negócio (resumo)

| Regra | Comportamento para grupos | |--------|--------------------------------| | Formato do id | groupId deve terminar em @g.us (validação Zod). | | Ambiente TEST | Proibido enviar para grupo. Resposta 403 com GROUP_NOT_ALLOWED_IN_TEST. Em TEST, apenas números dos usuários do tenant (perfil) + número Pilot Status, quando aplicável, são permitidos para contato individual. | | Ambiente LIVE e projeto | Exige productionApproved no projeto vinculado à API key, como para envio a contato (403 / PROJECT_NOT_APPROVED). | | Template | Deve existir no mesmo projeto/ambiente da chave, com versão aprovada conforme as regras já usadas para envio 1:1. | | LIVE + mídia no template | Se o corpo do template inclui mídia, aplica-se a regra de assinatura paga (402 / SUBSCRIPTION_REQUIRED_FOR_MEDIA), igual ao envio para número. | | Instância WhatsApp | A API key deve resolver para uma instância existente (ou o default EVOLUTION_INSTANCE_NAME); mesmas mensagens de erro que para contato (ex. instância vinculada inexistente → 409 com LINKED_WHATSAPP_INSTANCE_NOT_FOUND). | | Opt-in transacional | Para grupos, a checagem de opt-in transacional (WhatsAppTransactionalOptInService.assertDestinationAuthorized) não é aplicada — o destino de grupo entra no “bypass” de opt-in. A existência/validade do JID no WhatsApp é tratada no worker no momento do envio. | | Template MARKETING + número Pilot Status (instância padrão) | Se o template é MARKETING e a instância usada é a padrão da plataforma (“Pilot Status”): o envio só é permitido se (a) for chamada com sessão de dashboard e o usuário for admin da plataforma, ou (b) a API key tiver sido criada por um usuário admin. Caso contrário, 403 com TEMPLATE_CATEGORY_MARKETING_REQUIRES_OWN_NUMBER. | | Template OTP + número Pilot Status | Comportamento especial de bypass de opt-in para contatos não se aplica da mesma forma a grupos; para grupos já não há assert de opt-in. | | Rate limit | Aplica-se ao destino; para grupo, o “número de destino” no limitador é o próprio JID (string do grupo). | | Labels (opcional na API pública) | Se labels for enviado, o job assíncrono usa destinationType: "GROUP" e o destino é o groupId. Com retentionDays = 0 na chave, o vínculo pode não persistir (PII), como na documentação pública. | | Agendamento | deliverAt e deliverUntil seguem a mesma semântica dos envios para contato. |


Códigos HTTP / code úteis (grupos)

  • 400 — validação Zod (ex. groupId e destinationNumber juntos, ou nenhum dos dois; formato de groupId).
  • 403GROUP_NOT_ALLOWED_IN_TEST; opt-in (não aplica a grupo com bypass, mas aplica a contatos); TEMPLATE_CATEGORY_MARKETING_REQUIRES_OWN_NUMBER; outras regras de acesso.
  • 402 — mídia em LIVE sem assinatura (SUBSCRIPTION_REQUIRED_FOR_MEDIA).
  • 404 / 409 / 429 / 500 — mesmas famílias de erro do envio 1:1, conforme o caso.

Referências de código

| Tema | Onde | |------|------| | Schema compartilhado groupId / destinationNumber | packages/shared/src/validation.tssendMessageSchema | | Lógica completa pós-parse | apps/fullstack/src/app/api/v1/messages/send/route.tshandleSendWithKeyInfo | | Rota interna (dashboard) | apps/fullstack/src/app/api/internal/messages/send/route.ts | | Detecção @g.us no front | apps/fullstack/src/spa/pages/Messages.tsxsendOne | | Documentação de API pública (cliente) | apps/fullstack/public/llms/docs-api/06-send-messages.md |


Relacionados