LGPD em Chatbots: Checklist de Seguranca e Conformidade
Guia tecnico para adequar seu chatbot a LGPD. Consentimento, retencao de dados, direitos do titular e implementacao pratica.
Chatbot que coleta dados pessoais precisa seguir a LGPD. Este guia cobre os requisitos tecnicos e como implementar cada um.
O Que a LGPD Exige de Chatbots
Dados Pessoais em Chatbots
| Tipo | Exemplos | Classificacao |
|---|---|---|
| Identificacao | Nome, CPF, RG | Pessoal |
| Contato | Telefone, email, endereco | Pessoal |
| Financeiro | Cartao, conta bancaria | Pessoal Sensivel |
| Saude | Sintomas, receitas | Pessoal Sensivel |
| Localizacao | GPS, cidade | Pessoal |
| Comportamental | Historico de compras | Pessoal |
Bases Legais Aplicaveis
Para chatbots, as bases mais comuns sao:
- Consentimento: Usuario autoriza explicitamente
- Execucao de contrato: Necessario para prestar servico
- Legitimo interesse: Beneficio mutuo claro
Checklist Tecnico de Conformidade
1. Coleta de Consentimento
Fluxo de Opt-in
const CONSENT_MESSAGE = `
Antes de continuar, preciso da sua autorizacao.
Vou coletar e processar seus dados (nome, telefone, historico de conversas) para:
- Atender suas solicitacoes
- Melhorar nosso atendimento
- Enviar atualizacoes sobre seus pedidos
Seus dados sao protegidos conforme nossa Politica de Privacidade: https://seusite.com/privacidade
Voce concorda? Responda SIM ou NAO.
`;
async function handleFirstContact(userId, message) {
const consent = await getConsent(userId);
if (!consent) {
// Primeiro contato - pedir consentimento
await sendMessage(userId, CONSENT_MESSAGE);
await setAwaitingConsent(userId, true);
return;
}
if (consent.awaitingResponse) {
return handleConsentResponse(userId, message);
}
// Consentimento ja dado - processar normalmente
return processMessage(userId, message);
}
async function handleConsentResponse(userId, response) {
const normalized = response.trim().toUpperCase();
if (normalized === 'SIM') {
await saveConsent(userId, {
granted: true,
timestamp: new Date(),
version: '1.0',
ip: null, // WhatsApp nao fornece
method: 'whatsapp_chat'
});
await sendMessage(userId, 'Obrigado! Como posso ajudar?');
} else if (normalized === 'NAO') {
await saveConsent(userId, {
granted: false,
timestamp: new Date()
});
await sendMessage(userId,
'Entendido. Sem sua autorizacao, nao podemos prosseguir com o atendimento. ' +
'Se mudar de ideia, e so enviar uma mensagem.'
);
} else {
await sendMessage(userId,
'Por favor, responda SIM para autorizar ou NAO para recusar.'
);
}
}
Registro de Consentimento
// Schema MongoDB
const consentSchema = new mongoose.Schema({
userId: { type: String, required: true, index: true },
granted: { type: Boolean, required: true },
timestamp: { type: Date, default: Date.now },
version: String, // Versao da politica aceita
purposes: [String], // Finalidades autorizadas
method: String, // Como foi coletado
withdrawnAt: Date, // Se revogou
history: [{
action: String,
timestamp: Date,
details: Object
}]
});
// Funcoes
async function saveConsent(userId, data) {
const consent = await Consent.findOne({ userId });
if (consent) {
consent.history.push({
action: data.granted ? 'granted' : 'denied',
timestamp: new Date(),
details: data
});
consent.granted = data.granted;
consent.timestamp = new Date();
await consent.save();
} else {
await Consent.create({
userId,
...data,
history: [{
action: 'initial',
timestamp: new Date(),
details: data
}]
});
}
}
async function withdrawConsent(userId) {
const consent = await Consent.findOne({ userId });
if (consent) {
consent.granted = false;
consent.withdrawnAt = new Date();
consent.history.push({
action: 'withdrawn',
timestamp: new Date()
});
await consent.save();
}
}
2. Direitos do Titular
A LGPD garante direitos que seu chatbot deve suportar:
Menu de Privacidade
const PRIVACY_MENU = `
*Seus Direitos de Privacidade*
1 - Ver meus dados
2 - Corrigir meus dados
3 - Deletar meus dados
4 - Exportar meus dados
5 - Revogar consentimento
6 - Falar com humano
Escolha uma opcao:
`;
async function handlePrivacyRequest(userId, option) {
switch (option) {
case '1':
return handleAccessRequest(userId);
case '2':
return handleCorrectionRequest(userId);
case '3':
return handleDeletionRequest(userId);
case '4':
return handleExportRequest(userId);
case '5':
return handleConsentWithdrawal(userId);
case '6':
return escalateToHuman(userId, 'privacy_request');
}
}
Acesso aos Dados
async function handleAccessRequest(userId) {
// Coletar todos os dados do usuario
const userData = {
profile: await getProfile(userId),
conversations: await getConversations(userId, { limit: 100 }),
orders: await getOrders(userId),
consents: await getConsentHistory(userId)
};
// Formatar para exibicao
const summary = `
*Seus Dados Armazenados*
Nome: ${userData.profile.name || 'Nao informado'}
Telefone: ${userId}
Email: ${userData.profile.email || 'Nao informado'}
Conversas armazenadas: ${userData.conversations.length}
Pedidos registrados: ${userData.orders.length}
Consentimento: ${userData.consents.granted ? 'Ativo' : 'Revogado'}
Data: ${userData.consents.timestamp}
Para relatorio completo em PDF, responda EXPORTAR.
`;
await sendMessage(userId, summary);
}
Exclusao de Dados
async function handleDeletionRequest(userId) {
// Confirmar antes de deletar
await sendMessage(userId,
'Voce tem certeza que deseja excluir todos os seus dados? ' +
'Esta acao e irreversivel.\n\n' +
'Responda CONFIRMAR para prosseguir.'
);
await setAwaitingDeletionConfirmation(userId, true);
}
async function confirmDeletion(userId) {
// Anonimizar dados (nao deletar completamente para auditoria)
await anonymizeUser(userId);
// Log da exclusao (obrigatorio manter registro da acao)
await logDeletionRequest({
originalUserId: hashUserId(userId), // Hash para nao identificar
requestedAt: new Date(),
executedAt: new Date(),
dataTypes: ['profile', 'conversations', 'orders']
});
await sendMessage(userId,
'Seus dados foram excluidos. ' +
'Mantemos apenas um registro anonimo desta solicitacao por obrigacao legal.'
);
}
async function anonymizeUser(userId) {
// Anonimizar perfil
await db.profiles.updateOne(
{ userId },
{
$set: {
name: '[REMOVIDO]',
email: '[REMOVIDO]',
phone: hashUserId(userId),
anonymizedAt: new Date()
}
}
);
// Anonimizar conversas
await db.conversations.updateMany(
{ userId },
{
$set: {
'messages.$[].content': '[REMOVIDO]',
anonymizedAt: new Date()
}
}
);
// Revogar consentimento
await withdrawConsent(userId);
}
Exportacao (Portabilidade)
async function handleExportRequest(userId) {
// Gerar arquivo com todos os dados
const userData = await collectAllUserData(userId);
const exportData = {
exportDate: new Date().toISOString(),
userId: userId,
data: userData
};
// Salvar como JSON
const fileName = `dados_${hashUserId(userId)}_${Date.now()}.json`;
const filePath = `/tmp/exports/${fileName}`;
fs.writeFileSync(filePath, JSON.stringify(exportData, null, 2));
// Enviar link seguro (expira em 24h)
const downloadUrl = await createSecureDownloadLink(filePath, 24 * 60 * 60);
await sendMessage(userId,
`Seus dados estao prontos para download:\n${downloadUrl}\n\n` +
'Link valido por 24 horas.'
);
// Agendar exclusao do arquivo
setTimeout(() => fs.unlinkSync(filePath), 24 * 60 * 60 * 1000);
}
3. Retencao de Dados
Politica de Retencao
const RETENTION_POLICY = {
conversations: {
active: 90, // Dias para manter conversas ativas
archived: 365, // Dias para manter arquivadas
afterConsent: 30 // Dias apos revogacao
},
orders: {
completed: 5 * 365, // 5 anos (obrigacao fiscal)
cancelled: 365
},
logs: {
access: 180,
security: 365
}
};
// Job de limpeza (rodar diariamente)
async function cleanupExpiredData() {
const now = new Date();
// Limpar conversas antigas
const conversationCutoff = new Date(
now - RETENTION_POLICY.conversations.archived * 24 * 60 * 60 * 1000
);
await db.conversations.deleteMany({
updatedAt: { $lt: conversationCutoff },
status: 'archived'
});
// Anonimizar usuarios com consentimento revogado ha mais de 30 dias
const consentCutoff = new Date(
now - RETENTION_POLICY.conversations.afterConsent * 24 * 60 * 60 * 1000
);
const expiredConsents = await Consent.find({
granted: false,
withdrawnAt: { $lt: consentCutoff }
});
for (const consent of expiredConsents) {
await anonymizeUser(consent.userId);
}
console.log(`Limpeza concluida: ${expiredConsents.length} usuarios anonimizados`);
}
// Agendar job
const cron = require('node-cron');
cron.schedule('0 3 * * *', cleanupExpiredData); // 3h da manha
4. Seguranca dos Dados
Criptografia em Transito
const https = require('https');
const fs = require('fs');
// Servidor HTTPS obrigatorio
const server = https.createServer({
key: fs.readFileSync('/path/to/private.key'),
cert: fs.readFileSync('/path/to/certificate.crt'),
minVersion: 'TLSv1.2' // Minimo TLS 1.2
}, app);
Criptografia em Repouso
const crypto = require('crypto');
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // 32 bytes
const IV_LENGTH = 16;
function encrypt(text) {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag().toString('hex');
return `${iv.toString('hex')}:${authTag}:${encrypted}`;
}
function decrypt(encryptedText) {
const [ivHex, authTagHex, encrypted] = encryptedText.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Uso no schema
const messageSchema = new mongoose.Schema({
userId: String,
content: {
type: String,
set: encrypt,
get: decrypt
}
});
Mascaramento de Dados Sensiveis
function maskSensitiveData(data) {
return {
...data,
cpf: data.cpf ? maskCPF(data.cpf) : null,
phone: data.phone ? maskPhone(data.phone) : null,
email: data.email ? maskEmail(data.email) : null,
creditCard: data.creditCard ? maskCard(data.creditCard) : null
};
}
function maskCPF(cpf) {
// 123.456.789-00 -> ***.***.789-**
return cpf.replace(/(\d{3})\.(\d{3})\.(\d{3})-(\d{2})/, '***.***.***-**');
}
function maskPhone(phone) {
// 11999999999 -> 11*****9999
return phone.slice(0, 2) + '*****' + phone.slice(-4);
}
function maskEmail(email) {
// usuario@email.com -> u***o@email.com
const [user, domain] = email.split('@');
return user[0] + '***' + user.slice(-1) + '@' + domain;
}
// Usar em logs
console.log('Usuario:', maskSensitiveData(userData));
5. Logs de Auditoria
const auditSchema = new mongoose.Schema({
timestamp: { type: Date, default: Date.now, index: true },
action: {
type: String,
enum: ['access', 'create', 'update', 'delete', 'export', 'consent'],
index: true
},
userId: { type: String, index: true }, // Usuario afetado
performedBy: String, // Quem executou (bot, admin, etc)
resource: String, // Tipo de dado
details: Object,
ip: String,
userAgent: String
});
async function logAudit(action, userId, details) {
await Audit.create({
action,
userId,
performedBy: 'chatbot',
resource: details.resource,
details: {
...details,
// Nao logar dados sensiveis
sensitiveFields: details.fields?.filter(f =>
['cpf', 'password', 'card'].includes(f)
).length || 0
}
});
}
// Uso
await logAudit('access', userId, {
resource: 'profile',
fields: ['name', 'email', 'phone']
});
await logAudit('export', userId, {
resource: 'all_data',
format: 'json',
requestedAt: new Date()
});
6. Incidentes de Seguranca
async function handleSecurityIncident(incident) {
// 1. Registrar incidente
const incidentRecord = await Incident.create({
type: incident.type,
severity: incident.severity,
affectedUsers: incident.users,
description: incident.description,
detectedAt: new Date(),
status: 'open'
});
// 2. Notificar equipe
await notifySecurityTeam(incidentRecord);
// 3. Se grave, notificar ANPD (72h)
if (incident.severity === 'high' && incident.affectsPersonalData) {
await scheduleANPDNotification(incidentRecord);
}
// 4. Se necessario, notificar usuarios afetados
if (incident.requiresUserNotification) {
for (const userId of incident.users) {
await notifyUser(userId, {
title: 'Aviso de Seguranca',
message: incident.userMessage
});
}
}
return incidentRecord;
}
Implementacao de DPO Virtual
// Comandos de privacidade reconhecidos
const PRIVACY_COMMANDS = [
'meus dados', 'minha privacidade', 'lgpd',
'deletar dados', 'excluir dados', 'apagar dados',
'exportar dados', 'baixar dados',
'revogar consentimento', 'cancelar autorizacao',
'falar com dpo', 'encarregado'
];
function isPrivacyRequest(message) {
const lower = message.toLowerCase();
return PRIVACY_COMMANDS.some(cmd => lower.includes(cmd));
}
async function handleMessage(userId, message) {
// Verificar se e solicitacao de privacidade
if (isPrivacyRequest(message)) {
return handlePrivacyRequest(userId, message);
}
// Processamento normal
return processNormalMessage(userId, message);
}
async function handlePrivacyRequest(userId, message) {
const lower = message.toLowerCase();
if (lower.includes('deletar') || lower.includes('excluir') || lower.includes('apagar')) {
return handleDeletionRequest(userId);
}
if (lower.includes('exportar') || lower.includes('baixar')) {
return handleExportRequest(userId);
}
if (lower.includes('revogar') || lower.includes('cancelar')) {
return handleConsentWithdrawal(userId);
}
if (lower.includes('dpo') || lower.includes('encarregado')) {
return provideDPOContact(userId);
}
// Menu geral de privacidade
return sendMessage(userId, PRIVACY_MENU);
}
function provideDPOContact(userId) {
return sendMessage(userId,
'*Encarregado de Dados (DPO)*\n\n' +
'Nome: [Nome do DPO]\n' +
'Email: dpo@suaempresa.com.br\n' +
'Telefone: (11) 1234-5678\n\n' +
'Horario: Segunda a Sexta, 9h-18h'
);
}
Checklist Final
Coleta
- Consentimento explicito antes de coletar dados
- Finalidade clara e especifica
- Coleta minima (so o necessario)
- Registro de consentimento com timestamp
Armazenamento
- Criptografia em transito (HTTPS/TLS 1.2+)
- Criptografia em repouso para dados sensiveis
- Backup com mesma protecao
- Politica de retencao implementada
Direitos do Titular
- Acesso aos dados (visualizacao)
- Correcao de dados
- Exclusao/anonimizacao
- Portabilidade (export)
- Revogacao de consentimento
Seguranca
- Logs de auditoria
- Controle de acesso
- Mascaramento em logs
- Plano de resposta a incidentes
- Contato do DPO disponivel
Documentacao
- Politica de privacidade acessivel
- Termos de uso claros
- Registro de operacoes de tratamento
- Relatorio de impacto (se aplicavel)
Penalidades
| Infracao | Multa |
|---|---|
| Advertencia | - |
| Multa simples | Ate 2% do faturamento (max R$ 50M) |
| Multa diaria | Ate 2% do faturamento/dia |
| Bloqueio de dados | - |
| Eliminacao de dados | - |
Prevenir e mais barato que remediar.
Sakaguchi IA - Inteligencia Artificial para Empresas Brasileiras