Skip to content

API de Notificações - Contrato para Backend

Visão Geral

Sistema de notificações internas da plataforma. Cada notificação pertence a um usuário dentro de uma empresa e possui um escopo que define o contexto (empresa, contrato, pessoal, função ou ação).

As notificações suportam dados dinâmicos via coluna activity (JSONB), permitindo links de navegação e metadados sem alterar o schema.

Autenticação: Todas as rotas requerem token JWT padrão + header x-company-id.


Tabela notifications

ColunaTipoNullableIndexNotas
idUUIDNOPK
company_idUUIDNOYESFK companies
user_idUUIDNOYESUsuário destinatário
scopeVARCHAR(20)NOYEScompany | contract | personal | role | action
typeVARCHAR(50)NOYESTipo da notificação (ver lista abaixo)
titleVARCHAR(255)NO
messageTEXTNO
priorityVARCHAR(10)NOlow | medium | high | urgent
is_readBOOLEANNOYESDefault false
read_atTIMESTAMPYES
activityJSONBYESDados dinâmicos/links (ver schema abaixo)
actor_idUUIDYESFK users - quem gerou a notificação
actor_nameVARCHAR(255)YESDenormalizado para performance
actor_avatarVARCHAR(500)YESURL denormalizada
created_atTIMESTAMPNOYES
updated_atTIMESTAMPNO

Index composto: (company_id, user_id, is_read, created_at DESC)

Tipos de Notificação (coluna type)

TipoEscopo típicoDescrição
contract_createdcontractContrato criado
contract_publishedcontractContrato publicado
contract_signedcontractContrato assinado
contract_completedcontractContrato concluído
contract_cancelledcontractContrato cancelado
signatory_addedcontractSignatário adicionado
signature_requestedactionAssinatura solicitada
signature_completedcontractTodas assinaturas coletadas
document_uploadedcontractDocumento enviado
document_approvedcontractDocumento aprovado
document_rejectedcontractDocumento rejeitado
review_requestedactionRevisão solicitada
review_approvedcontractRevisão aprovada
review_rejectedcontractRevisão rejeitada
comment_addedpersonalComentário adicionado
user_invitedcompanyUsuário convidado
role_changedroleFunção alterada
login_alertpersonalAlerta de login
system_alertcompanyAlerta do sistema
action_requiredactionAção necessária
custompersonalMensagem customizada enviada via POST /notifications/custom
reminderactionLembrete
integration_records_without_contractcompanyCadastros sincronizados de uma integração ERP/CRM sem contrato ativo (varredura diária; ver activity.connectionId)

Schema do activity (JSONB)

json
{
  "entityType": "contract | template | document | user",
  "entityId": "uuid",
  "entityName": "Nome da entidade relacionada",
  "links": [
    {
      "label": "Ver Contrato",
      "route": "/contracts/:id",
      "params": { "id": "uuid" }
    }
  ],
  "actionLabel": "Assinar Agora",
  "actionRoute": "/contracts/uuid-do-contrato",
  "metadata": {
    "key": "value"
  }
}

Todos os campos são opcionais. O frontend usa actionRoute para navegação ao clicar na notificação.

Para integration_records_without_contract, o activity carrega kind (= o próprio type), connectionId, provider, pendingCount e actionRoute = /company/:companyId/integrations/:connectionId/status. O web navega pelo actionRoute; o app RN abre a mesma rota em WebView autenticado (prefixo /company/ permitido); o botão CTA do WhatsApp e o link do e-mail são derivados dessa rota (e-mail resolve o subdomínio do tenant via TenantUrlService).


Endpoints

1. GET /notifications

Lista paginada de notificações do usuário autenticado na empresa atual.

Headers:

Authorization: Bearer {token}
x-company-id: {companyId}

Query Parameters:

ParamTipoObrigatórioDefaultDescrição
scopestringNãoallFiltrar por escopo: company | contract | personal | role | action | all
isReadbooleanNão-Filtrar por status de leitura
pagenumberNão1Página atual
perPagenumberNão20Itens por página (max: 50)

Regras:

  • Filtrar por company_id do header e user_id do token
  • Ordenar por created_at DESC
  • Paginação obrigatória

Response 200:

json
{
  "data": {
    "data": [
      {
        "id": "uuid",
        "companyId": "uuid",
        "userId": "uuid",
        "scope": "contract",
        "type": "contract_created",
        "title": "Novo contrato criado",
        "message": "O contrato 'Acordo de Serviços - TechCorp' foi criado.",
        "priority": "medium",
        "isRead": false,
        "readAt": null,
        "activity": {
          "entityType": "contract",
          "entityId": "uuid",
          "entityName": "Acordo de Serviços - TechCorp",
          "actionLabel": "Ver Contrato",
          "actionRoute": "/contracts/uuid"
        },
        "actorId": "uuid",
        "actorName": "Maria Silva",
        "actorAvatar": "https://storage.example.com/avatars/maria.jpg",
        "createdAt": "2024-01-20T14:30:00Z",
        "updatedAt": "2024-01-20T14:30:00Z"
      }
    ],
    "total": 45,
    "page": 1,
    "perPage": 20,
    "totalPages": 3
  }
}

2. GET /notifications/unread-count

Retorna a contagem de notificações não lidas do usuário na empresa atual.

Headers:

Authorization: Bearer {token}
x-company-id: {companyId}

Regras:

  • Contar onde company_id = header, user_id = token, is_read = false

Response 200:

json
{
  "data": {
    "count": 12
  }
}

3. PATCH /notifications/:id/read

Marca uma notificação individual como lida.

Headers:

Authorization: Bearer {token}
x-company-id: {companyId}

Regras:

  • Verificar que a notificação pertence ao user_id do token e company_id do header
  • Setar is_read = true e read_at = now()
  • Se já estiver lida, retornar 200 sem alteração (idempotente)

Response 200:

json
{
  "data": {
    "id": "uuid",
    "companyId": "uuid",
    "userId": "uuid",
    "scope": "contract",
    "type": "contract_created",
    "title": "Novo contrato criado",
    "message": "O contrato 'Acordo de Serviços' foi criado.",
    "priority": "medium",
    "isRead": true,
    "readAt": "2024-01-20T15:00:00Z",
    "activity": null,
    "actorId": "uuid",
    "actorName": "Maria Silva",
    "actorAvatar": null,
    "createdAt": "2024-01-20T14:30:00Z",
    "updatedAt": "2024-01-20T15:00:00Z"
  }
}

Response 404:

json
{
  "message": "Notificação não encontrada"
}

4. PATCH /notifications/read-all

Marca todas as notificações não lidas do usuário como lidas.

Headers:

Authorization: Bearer {token}
x-company-id: {companyId}

Regras:

  • Atualizar em batch: UPDATE notifications SET is_read = true, read_at = now() WHERE company_id = :companyId AND user_id = :userId AND is_read = false

Response 200:

json
{
  "data": {
    "success": true
  }
}

5. POST /notifications/custom

Dispara uma notificação customizada a um usuário por um ou mais canais: notificação no app (platform), e-mail e/ou WhatsApp. Mensagem livre (aceita HTML, usado no corpo do e-mail; convertida para texto no WhatsApp e na notificação do app). Endpoint genérico — usado por web, mobile e pela IA (Zelor).

Headers:

Authorization: Bearer {token}
x-company-id: {companyId}

Body:

json
{
  "sendTo": "uuid-do-usuario",
  "message": "<p>Olá, Maria.</p><p>Aqui é a Maria Gestora, sua gestora de contratos. Identifiquei que as horas deste mês ainda não foram lançadas.</p><p>Para não impactar o pagamento das suas horas conforme previsto contratualmente, atualize seus apontamentos em até 5 dias corridos.</p>",
  "platforms": ["whatsapp", "platform", "email"],
  "title": "Maria, suas horas deste mês ainda não foram lançadas",
  "sender": { "name": "Maria Gestora", "role": "Gestora de contratos" }
}
CampoTipoNotas
sendToUUIDuserId do destinatário (quando empresa, o userId do representante)
messagestringTexto livre em HTML (<p>, <strong>, <br>). Deve ser uma carta direta: abre nomeando o destinatário e identifica o remetente (nome + papel) no próprio corpo; não repete o título; nunca usa "remuneração"/termos de vínculo CLT. Máx. 5000
platformsstring[]Lista com um, dois ou os três: whatsapp, platform, email. Não pode ser vazio
titlestringOpcional. Assunto do e-mail e título da notificação in-app; deve citar o destinatário. Fallback: 1ª linha da mensagem. Máx. 255
senderobjectOpcional. { name?, role? } — quem pediu o envio. Usado como autor (actorName) da notificação in-app

Comportamento:

  • Resolve o usuário por sendTo (404 se não existir). Se for um companyId, usa o usuário OWNER/ADMIN da empresa.
  • title define o assunto do e-mail e o título da notificação in-app (fallback: 1ª linha da mensagem; "Você recebeu uma mensagem" se vazia). Não é repetido como heading no corpo do e-mail.
  • sender vira o autor da notificação in-app (actorName = nome (papel)). No e-mail, a identificação do remetente fica na própria carta (composta na message), sem linha automática extra.
  • plataforma → cria notificação in-app (type: custom, scope personal) reusando o pipeline de notificações (gateway + push). A notificação é gravada com o companyId do destinatário (não o do remetente), para aparecer na lista e na contagem de não lidas dele.
  • email → renderiza a message (carta HTML) no layout Handlebars da plataforma (base + branding) e envia via SES (HTML + texto). Pulado se o usuário não tiver e-mail. PJ recebe sempre no e-mail do usuário owner, nunca no e-mail da empresa.
  • whatsapp → envia pelo bot outbound (texto). Pulado se o usuário não tiver telefone ou o bot não estiver configurado.
  • Cada canal é independente: a falha de um não impede os outros.

Response 200:

json
{
  "data": {
    "sendTo": "uuid",
    "dispatched": true,
    "channels": [
      { "platform": "platform", "dispatched": true },
      { "platform": "email", "dispatched": true },
      { "platform": "whatsapp", "dispatched": false, "skippedReason": "usuário sem telefone" }
    ]
  }
}

Preferências de Notificação

Sistema de preferências em dois níveis. Regras de negócio em Preferências de notificação.

Canais: whatsapp | sms | email | platform (objeto de flags booleanas). Persistência em tabelas relacionais dedicadas (user_notification_settings, company_notification_settings, company_notification_channels) — sem colunas novas em user_settings/notifications. Ausência de linha = ligado/permitido por padrão.

6. GET /notifications/preferences

Preferências de canal do usuário autenticado, mais a allowlist de canais da empresa atual (para desabilitar canais bloqueados na UI).

Headers: Authorization: Bearer {token} + x-company-id: {companyId}

Response 200:

json
{
  "data": {
    "channels": { "whatsapp": true, "sms": true, "email": true, "platform": true },
    "companyAllowedChannels": { "whatsapp": false, "sms": true, "email": true, "platform": true }
  }
}

7. PUT /notifications/preferences

Atualiza os canais pelos quais o usuário quer ser alcançado.

Headers: Authorization: Bearer {token} + x-company-id: {companyId}

Body:

json
{
  "channels": { "whatsapp": false, "sms": true, "email": true, "platform": true }
}

Response 200: mesmo formato do GET /notifications/preferences.

8. GET /company/:id/notification-settings

Configuração de notificação da empresa. Restrito a dono/administrador (403 caso contrário).

Headers: Authorization: Bearer {token} + x-company-id: {companyId}

Response 200:

json
{
  "data": {
    "allowedChannels": { "whatsapp": true, "sms": true, "email": true, "platform": true },
    "types": { "contract_created": true, "subscription_renewed": false }
  }
}

types traz apenas os tipos com override salvo; tipos ausentes valem como ligados. Tipos obrigatórios não aparecem (nunca são gatados).

9. PUT /company/:id/notification-settings

Atualiza a allowlist de canais e os tipos ativos da empresa. Restrito a dono/administrador.

Headers: Authorization: Bearer {token} + x-company-id: {companyId}

Body:

json
{
  "allowedChannels": { "whatsapp": false, "sms": true, "email": true, "platform": true },
  "types": { "contract_created": true, "subscription_renewed": false }
}

Response 200: mesmo formato do GET /company/:id/notification-settings.

Enforcement

A entrega efetiva de uma notificação de type por channel para um usuário obedece: type obrigatório OU (type ativo na empresa E channel permitido pela empresa E channel ligado pelo usuário). O gate é aplicado no pipeline de notificação in-app/push (NotificationService.create/createForUsers, canal platform) e no envio custom multi-canal (POST /notifications/custom, por canal escolhido). Notificações obrigatórias ignoram todos os gates.

Criação de Notificações (Backend Interno)

O backend deve criar notificações automaticamente nos seguintes eventos:

EventoTipoEscopoDestinatário
Contrato criadocontract_createdcontractMembros da empresa
Contrato publicadocontract_publishedcontractPartes envolvidas
Contrato assinado por alguémcontract_signedcontractDono do contrato
Todas assinaturas coletadascontract_completedcontractTodos envolvidos
Contrato canceladocontract_cancelledcontractTodos envolvidos
Signatário adicionadosignatory_addedcontractSignatário
Assinatura solicitadasignature_requestedactionSignatário
Assinatura concluídasignature_completedcontractDono do contrato
Documento enviadodocument_uploadedcontractRevisores
Documento aprovadodocument_approvedcontractQuem enviou
Documento rejeitadodocument_rejectedcontractQuem enviou
Revisão solicitadareview_requestedactionRevisor
Revisão aprovadareview_approvedcontractDono do contrato
Revisão rejeitadareview_rejectedcontractDono do contrato
Comentário adicionadocomment_addedpersonalDono do contrato
Usuário convidadouser_invitedcompanyAdmins da empresa
Função alteradarole_changedroleUsuário afetado
Login de novo dispositivologin_alertpersonalUsuário
Alerta do sistemasystem_alertcompanyTodos da empresa

Template sugerido para activity

Ao criar a notificação, popular o campo activity com os dados relevantes:

typescript
// Exemplo: contrato criado
{
  entityType: 'contract',
  entityId: contract.id,
  entityName: contract.name,
  actionLabel: 'Ver Contrato',
  actionRoute: `/contracts/${contract.id}`
}

// Exemplo: assinatura solicitada
{
  entityType: 'contract',
  entityId: contract.id,
  entityName: contract.name,
  actionLabel: 'Assinar Agora',
  actionRoute: `/contracts/${contract.id}`
}

// Exemplo: login alert
{
  metadata: {
    browser: 'Chrome',
    os: 'Windows',
    ip: '192.168.1.100'
  }
}

Considerações

  • Retenção: Definir política de retenção (ex: 90 dias). Notificações antigas podem ser deletadas via cron job.
  • Performance: O index composto (company_id, user_id, is_read, created_at DESC) é essencial para a query principal e contagem de não lidos.
  • Denormalização: actor_name e actor_avatar são denormalizados para evitar joins desnecessários na listagem. Atualizar via evento quando o usuário alterar nome/avatar.
  • Batch insert: Para notificações enviadas a múltiplos usuários (ex: system_alert), usar insert em batch.